├── .gitignore ├── Cargo.toml ├── LICENSE ├── assets └── FiraCode-Medium.ttf ├── modulator_play.code-workspace ├── readme.md └── src ├── bezier.rs ├── main.rs └── prims.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "modulator_play" 3 | version = "0.1.0" 4 | 5 | description = "A Rust playground for experimenting with types from the Modulator crate" 6 | authors = ["Andrea Pessino "] 7 | categories = ["simulation", "game-engines", "gui"] 8 | 9 | keywords = ["modulation", "LFO", "wave", "generator", "animation", "spring", "physics", "game", "engine", "UI", "piston", "piston_window"] 10 | 11 | license = "MIT" 12 | repository = "https://github.com/apessino/modulator_play" 13 | readme = "readme.md" 14 | 15 | [dependencies] 16 | modulator = "0.1.0" 17 | cgmath = "0.16.1" 18 | num-complex = "0.2.1" 19 | rand = "0.5.5" 20 | 21 | piston_window = "0.83.0" 22 | piston2d-gfx_graphics = "0.53.0" 23 | piston-shaders_graphics2d = "0.3.1" 24 | piston-gfx_texture = "0.34.0" 25 | shader_version = "0.3.0" 26 | gfx = "0.17.1" 27 | gfx_device_gl = "0.15.3" 28 | draw_state = "0.8.0" 29 | find_folder = "0.3.0" 30 | 31 | [dependencies.piston2d-graphics] 32 | version = "0.27.0" 33 | features = ["glyph_cache_rusttype"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrea Pessino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apessino/modulator_play/cdee89e92364e3f3fd063f0f1a2a8187870e750e/assets/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /modulator_play.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "cSpell.enabledLanguageIds": [ 9 | "asciidoc", 10 | "c", 11 | "cpp", 12 | "csharp", 13 | "css", 14 | "go", 15 | "handlebars", 16 | "html", 17 | "jade", 18 | "javascript", 19 | "javascriptreact", 20 | "json", 21 | "latex", 22 | "less", 23 | "markdown", 24 | "php", 25 | "plaintext", 26 | "pub", 27 | "python", 28 | "restructuredtext", 29 | "scss", 30 | "text", 31 | "typescript", 32 | "typescriptreact", 33 | "yml" 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Modulator Play 3 | 4 | CLICK HERE for a Video Introduction To The Modulator Crate And Play Application 6 | Check out this video for an introduction to the application and crate! 7 | 8 | An environment to visualize and test the modulator crate and to experiment with 9 | expressive 2d primitive rendering. Based on Piston Window, this application is 10 | meant to be both a test bed for the Modulator crate and its included source types, 11 | and a minimal friction environment to experiment with Rust coding. 12 | 13 | The best way to get familiar with this application, what it can do and how it relates to the Modulator crate, is to watch the video linked above. 14 | 15 | Assuming you have Rust properly installed (if not [go get it now, it is awesome](https://www.rust-lang.org)), to run the application, clone or download this repository, then ```cd``` to the directory and: 16 | 17 | cargo run --release 18 | 19 | You can skip the --release to build and run the application in debug. 20 | 21 | If you want to get the check out the Modulator crate and the types it provides: 22 | 23 | [CLICK HERE to go to the Modulator crate repository](https://github.com/apessino/modulator) 24 | 25 | Copyright© 2018 Ready At Dawn Studios 26 | 27 | NOTICE: this application includes the Fira Code Medium (ttf) font in its ```asset``` directory. Go [here](https://github.com/tonsky/FiraCode) for license/copyright information and to get this wonderful font. 28 | -------------------------------------------------------------------------------- /src/bezier.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # 2d Cubic Bezier curves using Shemanarev metrics 3 | //! 4 | //! Eventually this will be extended with curve split/join operations, maybe made generic in 5 | //! the sample type (even if it seems overkill), and then moved into its own crate. For now 6 | //! just a module that lives here in the modulator testbed app 7 | //! 8 | //! Copyright© 2018 Ready At Dawn Studios 9 | 10 | use std::f32; 11 | use std::f32::consts::*; 12 | 13 | use cgmath::prelude::*; 14 | use cgmath::{vec2, Vector2}; 15 | 16 | type Vec2 = Vector2; 17 | 18 | /// Maximum precision normalization or 0-vector 19 | pub fn vec2_norm_or_zero(v: Vec2) -> Vec2 { 20 | let sz = 1.0 / v[0].hypot(v[1]); 21 | if sz.is_finite() { 22 | v * sz 23 | } else { 24 | vec2(0.0, 0.0) 25 | } 26 | } 27 | 28 | /// Test if the given bounds enclose the point `v` 29 | pub fn vec2_in_bounds(v: Vec2, bnds: &[Vec2; 2]) -> bool { 30 | v.x >= bnds[0].x && v.x <= bnds[1].x && v.y >= bnds[0].y && v.y <= bnds[1].y 31 | } 32 | 33 | /// Calculate and return the closest point on line segment `l0..l1` to point `p`. 34 | /// Returns the point and its weight 35 | pub fn vec2_closest_on_line(p: Vec2, l0: Vec2, l1: Vec2) -> (Vec2, f32) { 36 | let ln = l1 - l0; 37 | let t = (p - l0).dot(ln); 38 | 39 | if t <= 0.0 { 40 | return (l0, 0.0); 41 | } 42 | 43 | let den = ln.magnitude2(); 44 | if t >= den { 45 | return (l1, 1.0); 46 | } 47 | 48 | let t = t / den; 49 | (l0 + ln * t, t) 50 | } 51 | 52 | /// Extended data stored per sample 53 | #[derive(Copy, Clone, Debug)] 54 | pub struct SampleData { 55 | pub t: f32, // normalized sample time along the curve 56 | pub l: f32, // linear distance to the next sample 57 | pub n: Vec2, // normal to the curve at the sample 58 | } 59 | 60 | impl SampleData { 61 | pub fn new(t: f32) -> Self { 62 | Self { 63 | t, 64 | l: 0.0, 65 | n: vec2(0.0, 1.0), 66 | } 67 | } 68 | } 69 | 70 | /// A 2d Bezier-style cubic curve using Shemanarev metrics for termination of recursion 71 | #[derive(Clone)] 72 | pub struct CubicCurve { 73 | controls: [Vec2; 4], // curve control points 74 | dc: [Vec2; 3], // controls derivative coefficients 75 | 76 | approx_scale: f32, // screen space approx. scale (increases with accuracy) 77 | angle_tolerance: f32, // the smaller, the smoother the turns, 0 to disable 78 | 79 | smp: Vec, // cached curve samples 80 | smd: Vec, // symmetrical cached sample data 81 | 82 | tolerance_squared: f32, // internal state for recursion 83 | total_length: f32, // cached total length of the curve, from samples 84 | bounds: [Vec2; 2], // bounding box (as a l,h quad) (around samples only) 85 | 86 | cached: bool, // state of the sample cache, all data above depends on this 87 | } 88 | 89 | const MAX_RECURSION: u32 = 32; 90 | const ANGLE_EPSILON: f32 = 0.01; 91 | 92 | impl CubicCurve { 93 | /// Make a new cubic curve 94 | pub fn new() -> Self { 95 | let approx_scale = 1.0; 96 | CubicCurve { 97 | controls: [vec2(0.0, 0.0); 4], 98 | dc: [vec2(0.0, 0.0); 3], 99 | approx_scale, 100 | angle_tolerance: 15.0_f32.to_radians(), 101 | smp: vec![], 102 | smd: vec![], 103 | tolerance_squared: 0.25 / (approx_scale * approx_scale), 104 | total_length: 0.0, 105 | bounds: [vec2(0.0, 0.0); 2], 106 | cached: true, 107 | } 108 | } 109 | 110 | /// Access the cubic curve control points 111 | pub fn control_points(&self) -> &[Vec2; 4] { 112 | &self.controls 113 | } 114 | /// Set the cubic curve control points 115 | pub fn set_control_points(&mut self, controls: &[Vec2; 4]) { 116 | self.controls = *controls; 117 | 118 | self.dc[0] = (self.controls[1] - self.controls[0]) * 3.0; 119 | self.dc[1] = (self.controls[2] - self.controls[1]) * 3.0; 120 | self.dc[2] = (self.controls[3] - self.controls[2]) * 3.0; 121 | 122 | self.invalidate(); 123 | } 124 | 125 | /// Screen space approximation scale (increases with accuracy) 126 | pub fn approx_scale(&self) -> f32 { 127 | self.approx_scale 128 | } 129 | /// Set the screen space approximation scale (increases with accuracy) 130 | pub fn set_approx_scale(&mut self, scale: f32) { 131 | self.approx_scale = f32::max(scale, 0.0001); 132 | self.tolerance_squared = 0.25 / (self.approx_scale * self.approx_scale); 133 | 134 | self.invalidate(); 135 | } 136 | 137 | /// Angle tolerance - the smaller it is, the smoother the turns, 0 to disable 138 | pub fn angle_tolerance(&self) -> f32 { 139 | self.angle_tolerance 140 | } 141 | /// Set angle tolerance - the smaller it is, the smoother the turns, 0 to disable 142 | pub fn set_angle_tolerance(&mut self, scale: f32) { 143 | self.angle_tolerance = scale; 144 | self.invalidate(); 145 | } 146 | 147 | /// Borrow the cached curve samples 148 | /// 149 | /// # Panics 150 | /// 151 | /// Curve must be `valid` or this method will panic 152 | pub fn samples(&self) -> &Vec { 153 | assert!(self.valid()); 154 | &self.smp 155 | } 156 | /// Borrow the cached curve additional sample data 157 | /// 158 | /// # Panics 159 | /// 160 | /// Curve must be `valid` or this method will panic 161 | pub fn sampledata(&self) -> &Vec { 162 | assert!(self.valid()); 163 | &self.smd 164 | } 165 | 166 | /// Total linear length of the curve, same magnitude of error as the samples 167 | /// 168 | /// # Panics 169 | /// 170 | /// Curve must be `valid` or this method will panic 171 | pub fn length(&self) -> f32 { 172 | assert!(self.valid()); 173 | self.total_length 174 | } 175 | /// AABB of sample points (no control points), extruded by some value (pass 0.0 for exact) 176 | /// 177 | /// # Panics 178 | /// 179 | /// Curve must be `valid` or this method will panic 180 | pub fn bounds(&self, extrude: f32) -> [Vec2; 2] { 181 | assert!(self.valid()); 182 | [ 183 | vec2(self.bounds[0].x - extrude, self.bounds[0].y - extrude), 184 | vec2(self.bounds[1].x + extrude, self.bounds[1].y + extrude), 185 | ] 186 | } 187 | /// Test if the given point is inside the bounds of the curve, with optional extrusion 188 | /// 189 | /// # Panics 190 | /// 191 | /// Curve must be `valid` or this method will panic 192 | pub fn in_bounds(&self, pt: Vec2, extrude: f32) -> bool { 193 | let bn = self.bounds(extrude); 194 | pt.x >= bn[0].x && pt.x <= bn[1].x && pt.y >= bn[0].y && pt.y <= bn[1].y 195 | } 196 | 197 | /// Point on curve at normalized time t (exact, not using samples) 198 | pub fn curve_at(&self, t: f32) -> Vec2 { 199 | let t2 = t * t; 200 | let t3 = t * t * t; 201 | 202 | let mt = 1.0 - t; 203 | let mt2 = mt * mt; 204 | let mt3 = mt * mt * mt; 205 | 206 | let c1 = 3.0 * mt2 * t; 207 | let c2 = 3.0 * mt * t2; 208 | 209 | vec2( 210 | mt3 * self.controls[0].x 211 | + c1 * self.controls[1].x 212 | + c2 * self.controls[2].x 213 | + t3 * self.controls[3].x, 214 | mt3 * self.controls[0].y 215 | + c1 * self.controls[1].y 216 | + c2 * self.controls[2].y 217 | + t3 * self.controls[3].y, 218 | ) 219 | } 220 | 221 | /// Tangent to curve at curve time t (exact, not using samples) 222 | pub fn tangent_at(&self, t: f32) -> Vec2 { 223 | let t2 = t * t; 224 | 225 | let mt = 1.0 - t; 226 | let mt2 = mt * mt; 227 | 228 | let c1 = 2.0 * mt * t; 229 | 230 | vec2_norm_or_zero(vec2( 231 | mt2 * self.dc[0].x + c1 * self.dc[1].x + t2 * self.dc[2].x, 232 | mt2 * self.dc[0].y + c1 * self.dc[1].y + t2 * self.dc[2].y, 233 | )) 234 | } 235 | /// Normal to curve at curve time t (exact, not using samples) 236 | pub fn normal_at(&self, t: f32) -> Vec2 { 237 | self.normal_at_(self.tangent_at(t)) 238 | } 239 | /// Get tangent if you have the normal (exact, not using samples) 240 | pub fn tangent_at_(&self, n: Vec2) -> Vec2 { 241 | vec2(n.y, -n.x) 242 | } 243 | /// Get normal if you have the tangent (exact, not using samples) 244 | pub fn normal_at_(&self, t: Vec2) -> Vec2 { 245 | vec2(-t.y, t.x) 246 | } 247 | 248 | /// Closest point on curve to `pt` (linear approximation, from samples), returns 249 | /// `(p, t = curve time [0..1], d = dist from pt to p, k = index of sample containing t)` 250 | /// 251 | /// # Panics 252 | /// 253 | /// Curve must be `valid` or this method will panic 254 | pub fn closest_to(&self, pt: Vec2) -> (Option, Vec2, f32, f32) { 255 | assert!(self.valid()); 256 | 257 | let mut p = vec2(f32::INFINITY, f32::INFINITY); 258 | let mut t = f32::INFINITY; 259 | let mut d = f32::INFINITY; 260 | let mut k = None; 261 | 262 | let n = self.smp.len(); 263 | let mut i = 0; 264 | for l0 in &self.smp { 265 | let not_last = i < n - 1; 266 | 267 | let l1 = if not_last { &self.smp[i + 1] } else { l0 }; 268 | let (p_, t_) = vec2_closest_on_line(pt, *l0, *l1); 269 | 270 | let d_ = (pt - p_).magnitude2(); 271 | if d_ < d { 272 | p = p_; 273 | t = if not_last { 274 | self.smd[i].t + (self.smd[i + 1].t - self.smd[i].t) * t_ 275 | } else { 276 | self.smd[i].t 277 | }; 278 | d = d_; 279 | k = Some(i); 280 | } 281 | i += 1; 282 | } 283 | 284 | (k, p, t, d.sqrt()) 285 | } 286 | 287 | /// Closest sample to `pt`, returns (closest sample index, its distance to pt) 288 | /// 289 | /// # Panics 290 | /// 291 | /// Curve must be `valid` or this method will panic 292 | pub fn closest_sample_to(&self, pt: Vec2) -> (Option, f32) { 293 | assert!(self.valid()); 294 | 295 | let mut d = f32::INFINITY; 296 | let mut k = None; 297 | 298 | let mut i = 0; 299 | for sp in &self.smp { 300 | let d_ = (pt - *sp).magnitude2(); 301 | if d_ < d { 302 | d = d_; 303 | k = Some(i); 304 | } 305 | i += 1; 306 | } 307 | 308 | (k, d.sqrt()) 309 | } 310 | 311 | /// Closest handle point to `pt` (a handle is the line between a pair of control points, 312 | /// from the point on curve to the one off curve), returns (handle 0 = c0..c1 1 = c3..c2, 313 | /// point on handle, time along handle of closest point [0.0..1.0], distance to pt) 314 | pub fn closest_handle_to(&self, pt: Vec2) -> (usize, Vec2, f32, f32) { 315 | let (p0, t0) = vec2_closest_on_line(pt, self.controls[0], self.controls[1]); 316 | let (p1, t1) = vec2_closest_on_line(pt, self.controls[3], self.controls[2]); 317 | 318 | let d0 = (pt - p0).magnitude2(); 319 | let d1 = (pt - p1).magnitude2(); 320 | 321 | if d1 < d0 { 322 | (1, p1, t1, d1.sqrt()) 323 | } else { 324 | (0, p0, t0, d0.sqrt()) 325 | } 326 | } 327 | 328 | /// Closest control point to pt, returns (control point index [0..3], distance to pt) 329 | pub fn closest_control_point_to(&self, pt: Vec2) -> (usize, f32) { 330 | let mut d = f32::INFINITY; 331 | let mut k = 0; 332 | 333 | for i in 0..4 { 334 | let d_ = (pt - self.controls[i]).magnitude2(); 335 | if d_ < d { 336 | d = d_; 337 | k = i; 338 | } 339 | } 340 | 341 | (k, d.sqrt()) 342 | } 343 | 344 | /// If true the cached curve data is valid 345 | pub fn valid(&self) -> bool { 346 | self.cached 347 | } 348 | /// Force the cached curve data to be recomputed 349 | pub fn invalidate(&mut self) { 350 | self.cached = false; 351 | } 352 | 353 | /// Validate the cached curved data - MUST be called to ensure the curve is valid before 354 | /// accessing any of the curve caches 355 | pub fn validate(&mut self) { 356 | if self.valid() == false { 357 | self.smp.clear(); 358 | self.smd.clear(); 359 | 360 | let t_ = self.initial_times(); // initial curve times 361 | let c_ = self.controls; 362 | 363 | self.smp.push(c_[0]); // c0 is always the first sample 364 | self.smd.push(SampleData::new(t_[0])); 365 | 366 | self.bezier(c_, t_, 0); 367 | 368 | self.smp.push(c_[3]); // c3 is always the last 369 | self.smd.push(SampleData::new(t_[3])); 370 | 371 | self.cache_sample_data(); 372 | self.cached = true; 373 | } 374 | } 375 | 376 | /// Compute the initial curve times for the recursion 377 | fn initial_times(&self) -> [f32; 4] { 378 | let c01 = self.controls[1] - self.controls[0]; 379 | let c12 = self.controls[2] - self.controls[1]; 380 | let c23 = self.controls[3] - self.controls[2]; 381 | 382 | let l01 = c01.magnitude(); 383 | let l12 = c12.magnitude(); 384 | let l23 = c23.magnitude(); 385 | 386 | let l = l01 + l12 + l23; 387 | 388 | let t1 = l01 / l; 389 | let t2 = l12 / l; 390 | 391 | [ 392 | 0.0, 393 | if t1.is_finite() { t1 } else { 0.0 }, 394 | if t2.is_finite() { t2 } else { 0.0 }, 395 | 1.0, 396 | ] 397 | } 398 | 399 | /// Recursive bezier evaluation using Shemanarev metrics 400 | fn bezier(&mut self, c_: [Vec2; 4], t_: [f32; 4], level: u32) { 401 | #![allow(non_snake_case)] 402 | 403 | if level > MAX_RECURSION { 404 | return; 405 | } 406 | 407 | let c__01 = (c_[0] + c_[1]) * 0.5; 408 | let c__12 = (c_[1] + c_[2]) * 0.5; 409 | let c__23 = (c_[2] + c_[3]) * 0.5; 410 | let c_012 = (c__01 + c__12) * 0.5; 411 | let c_123 = (c__12 + c__23) * 0.5; 412 | let c0123 = (c_012 + c_123) * 0.5; 413 | 414 | let t__01 = (t_[0] + t_[1]) * 0.5; 415 | let t__12 = (t_[1] + t_[2]) * 0.5; 416 | let t__23 = (t_[2] + t_[3]) * 0.5; 417 | let t_012 = (t__01 + t__12) * 0.5; 418 | let t_123 = (t__12 + t__23) * 0.5; 419 | let t0123 = (t_012 + t_123) * 0.5; 420 | 421 | let d = vec2(c_[3][0] - c_[0][0], c_[3][1] - c_[0][1]); 422 | let dsq = d.magnitude2() * self.tolerance_squared; 423 | 424 | let d2 = ((c_[1].x - c_[3].x) * d.y - (c_[1].y - c_[3].y) * d.x).abs(); 425 | let d3 = ((c_[2].x - c_[3].x) * d.y - (c_[2].y - c_[3].y) * d.x).abs(); 426 | 427 | let has_d2 = d2 > f32::EPSILON; 428 | let has_d3 = d3 > f32::EPSILON; 429 | 430 | if has_d3 == false && has_d2 == false { 431 | // all collinear, c0 == c3 432 | if self.collinear(c_, t_, d) == false { 433 | return; 434 | } 435 | } else if has_d3 == false && has_d2 { 436 | // c0, c1 and c3 collinear, c2 significant 437 | if d3 * d3 <= dsq && self.onlyc2c1(c_, t_, true) == false { 438 | return; 439 | } 440 | } else if has_d3 && has_d2 == false { 441 | // c0, c2 and c3 collinear, c1 significant 442 | if d2 * d2 <= dsq && self.onlyc2c1(c_, t_, false) == false { 443 | return; 444 | } 445 | } else if (d2 + d3) * (d2 + d3) <= dsq && self.curve(c_, t_) == false { 446 | return; 447 | } 448 | 449 | // recurse down each side of the curve 450 | self.bezier( 451 | [c_[0], c__01, c_012, c0123], 452 | [t_[0], t__01, t_012, t0123], 453 | level + 1, 454 | ); 455 | self.bezier( 456 | [c0123, c_123, c__23, c_[3]], 457 | [t0123, t_123, t__23, t_[3]], 458 | level + 1, 459 | ); 460 | } 461 | 462 | /// Local curve coefficients are collinear 463 | fn collinear(&mut self, c_: [Vec2; 4], t_: [f32; 4], d: Vec2) -> bool { 464 | let k = 1.0 / d.magnitude2(); 465 | 466 | let mut d2; 467 | let mut d3; 468 | 469 | if k.is_finite() == false { 470 | d2 = (c_[0] - c_[1]).magnitude2(); 471 | d3 = (c_[3] - c_[2]).magnitude2(); 472 | } else { 473 | d2 = k * ((c_[1].x - c_[0].x) * d.x + (c_[1].y - c_[0].y) * d.y); 474 | d3 = k * ((c_[2].x - c_[0].x) * d.x + (c_[2].y - c_[0].y) * d.y); 475 | 476 | if d2 > 0.0 && d2 < 1.0 && d3 > 0.0 && d3 < 1.0 { 477 | return false; // simple 1-2-3-4 collinear case, stop recursion 478 | } 479 | 480 | if d2 <= 0.0 { 481 | d2 = (c_[0] - c_[1]).magnitude2(); 482 | } else if d2 >= 1.0 { 483 | d2 = (c_[3] - c_[1]).magnitude2(); 484 | } else { 485 | d2 = (vec2(c_[0].x + d2 * d.x, c_[0].y + d2 * d.y) - c_[1]).magnitude2(); 486 | } 487 | 488 | if d3 <= 0.0 { 489 | d3 = (c_[0] - c_[2]).magnitude2(); 490 | } else if d3 >= 1.0 { 491 | d3 = (c_[3] - c_[2]).magnitude2(); 492 | } else { 493 | d3 = (vec2(c_[0].x + d3 * d.x, c_[0].y + d3 * d.y) - c_[2]).magnitude2(); 494 | } 495 | } 496 | 497 | if d2 > d3 { 498 | if d2 < self.tolerance_squared { 499 | self.smp.push(c_[1]); 500 | self.smd.push(SampleData::new(t_[1])); 501 | 502 | return false; 503 | } 504 | } else if d3 < self.tolerance_squared { 505 | self.smp.push(c_[2]); 506 | self.smd.push(SampleData::new(t_[2])); 507 | 508 | return false; 509 | } 510 | 511 | true 512 | } 513 | 514 | /// Only one of the mid controls is meaningful 515 | fn onlyc2c1(&mut self, c_: [Vec2; 4], t_: [f32; 4], onlyc2: bool) -> bool { 516 | if self.angle_tolerance < ANGLE_EPSILON { 517 | self.smp.push((c_[1] + c_[2]) * 0.5); 518 | self.smd.push(SampleData::new((t_[1] + t_[2]) * 0.5)); 519 | 520 | return false; 521 | } 522 | 523 | let mut da = if onlyc2 { 524 | ((c_[3].y - c_[2].y).atan2(c_[3].x - c_[2].x) 525 | - (c_[2].y - c_[1].y).atan2(c_[2].x - c_[1].x)).abs() 526 | } else { 527 | ((c_[2].y - c_[1].y).atan2(c_[2].x - c_[1].x) 528 | - (c_[1].y - c_[0].y).atan2(c_[1].x - c_[0].x)).abs() 529 | }; 530 | 531 | if da >= PI { 532 | da = PI * 2.0 - da; 533 | } 534 | 535 | if da < self.angle_tolerance { 536 | self.smp.push(c_[1]); 537 | self.smd.push(SampleData::new(t_[1])); 538 | 539 | self.smp.push(c_[2]); 540 | self.smd.push(SampleData::new(t_[2])); 541 | 542 | return false; 543 | } 544 | 545 | true 546 | } 547 | 548 | /// The control points fully define a curve 549 | fn curve(&mut self, c_: [Vec2; 4], t_: [f32; 4]) -> bool { 550 | if self.angle_tolerance < ANGLE_EPSILON { 551 | self.smp.push((c_[1] + c_[2]) * 0.5); 552 | self.smd.push(SampleData::new((t_[1] + t_[2]) * 0.5)); 553 | 554 | return false; 555 | } 556 | 557 | let d21 = (c_[2].y - c_[1].y).atan2(c_[2].x - c_[1].x); 558 | let mut da1 = (d21 - (c_[1].y - c_[0].y).atan2(c_[1].x - c_[0].x)).abs(); 559 | let mut da2 = ((c_[3].y - c_[2].y).atan2(c_[3].x - c_[2].x) - d21).abs(); 560 | 561 | if da1 >= PI { 562 | da1 = PI * 2.0 - da1; 563 | } 564 | if da2 >= PI { 565 | da2 = PI * 2.0 - da2; 566 | } 567 | 568 | if da1 + da2 < self.angle_tolerance { 569 | self.smp.push((c_[1] + c_[2]) * 0.5); 570 | self.smd.push(SampleData::new((t_[1] + t_[2]) * 0.5)); 571 | 572 | return false; 573 | } 574 | 575 | true 576 | } 577 | 578 | /// Compute the additional data cached for the curve samples 579 | fn cache_sample_data(&mut self) { 580 | self.total_length = 0.0; 581 | self.bounds = [ 582 | vec2(f32::INFINITY, f32::INFINITY), 583 | vec2(f32::NEG_INFINITY, f32::NEG_INFINITY), 584 | ]; 585 | 586 | let n = self.smp.len(); 587 | debug_assert!(n == self.smd.len()); 588 | 589 | for i in 0..n { 590 | self.smd[i].n = self.normal_at(self.smd[i].t); 591 | if i < n - 1 { 592 | self.smd[i].l = (self.smp[i + 1] - self.smp[i]).magnitude(); 593 | } 594 | 595 | let x = self.smp[i].x; 596 | let y = self.smp[i].y; 597 | 598 | if x < self.bounds[0].x { 599 | self.bounds[0].x = x; 600 | } 601 | if x > self.bounds[1].x { 602 | self.bounds[1].x = x; 603 | } 604 | if y < self.bounds[0].y { 605 | self.bounds[0].y = y; 606 | } 607 | if y > self.bounds[1].y { 608 | self.bounds[1].y = y; 609 | } 610 | 611 | self.total_length += self.smd[i].l; 612 | } 613 | } 614 | } 615 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Modulator Play 3 | //! 4 | //! An environment to visualize and test the modulator crate and to experiment with 5 | //! expressive 2d primitive rendering. Based on Piston Window, this application is 6 | //! meant to be both a test bed for the Modulator crate and its included source types, 7 | //! and a minimal friction environment to experiment with Rust coding. 8 | //! 9 | //! Copyright© 2018 Ready At Dawn Studios 10 | 11 | extern crate cgmath; 12 | extern crate find_folder; 13 | extern crate modulator; 14 | extern crate num_complex; 15 | extern crate rand; 16 | 17 | extern crate draw_state; 18 | extern crate gfx_device_gl; 19 | extern crate gfx_graphics; 20 | extern crate gfx_texture; 21 | extern crate graphics; 22 | extern crate piston_window; 23 | extern crate shader_version; 24 | extern crate shaders_graphics2d as shaders; 25 | 26 | #[macro_use] 27 | extern crate gfx; 28 | 29 | pub mod bezier; 30 | pub mod prims; 31 | 32 | use modulator::sources::{ 33 | Newtonian, ScalarGoalFollower, ScalarSpring, ShiftRegister, ShiftRegisterInterp, Wave, 34 | }; 35 | use modulator::{Modulator, ModulatorEnv}; 36 | 37 | use piston_window::character::CharacterCache; 38 | use piston_window::*; 39 | 40 | use rand::prelude::*; 41 | use std::{collections::HashMap, collections::VecDeque, f32, time::Instant}; 42 | 43 | use cgmath::prelude::*; 44 | use cgmath::{vec2, Basis2, Decomposed, Rad, Vector2}; 45 | 46 | type Vec2 = Vector2; 47 | type Bas2 = Basis2; 48 | type Dec2 = Decomposed; 49 | 50 | use bezier::CubicCurve; 51 | use prims::{PrimGraphics, Prims2d}; 52 | 53 | /// Application main loop 54 | fn main() { 55 | let mut window: PistonWindow = WindowSettings::new("modulator_play", (1920, 1080)) 56 | .samples(16) 57 | .exit_on_esc(true) 58 | .build() 59 | .unwrap_or_else(|e| panic!("error creating main window: {}", e)); 60 | 61 | let mut data = DrawData::new(&mut window); // all data related to drawing 62 | let mut state = StateData::new(); // all other data 63 | 64 | setup_modulators(&mut state); 65 | 66 | let mut earlier = Instant::now(); 67 | while let Some(e) = window.next() { 68 | let dt = time_delta(&mut earlier); // `earlier` updated in place 69 | update(&e, dt, &mut state, &mut data, &window); 70 | 71 | if let Some(args) = e.render_args() { 72 | window.window.make_current(); 73 | clear(data.theme[0], &mut data.draw_context(&mut window)); // clear window 74 | 75 | let c = Context::new_viewport(args.viewport()); // make a context, cache dimensions 76 | data.dims = c.get_view_size(); 77 | data.hdims = [data.dims[0] * 0.5, data.dims[1] * 0.5]; 78 | 79 | dispatch(&mut state, &mut data, &c, &mut window); // main update driver 80 | 81 | window.draw_2d(&e, |_, _| {}); // just so that window.g2d is flushed if needed 82 | window.encoder.flush(&mut window.device); 83 | }; 84 | } 85 | } 86 | 87 | /// Populate the modulator environments with modulators 88 | fn setup_modulators(st: &mut StateData) { 89 | // Make a wave modulator 90 | let wave = Wave::new(2.0, 0.5); // start with 2.0 amplitude and 0.5Hz frequency 91 | 92 | st.m1.take("waveform", Box::new(wave)); 93 | st.bufs.insert("waveform".to_string(), VecDeque::new()); 94 | 95 | set_wave_shape(WaveShape::Sine, st); 96 | 97 | // Make a random wave modulator 98 | let wave = Wave::new(2.0, 0.1).wave(Box::new(|w, _| { 99 | let n = w.value + thread_rng().gen_range(-w.frequency, w.frequency); 100 | f32::min(f32::max(n, -w.amplitude), w.amplitude) 101 | })); 102 | 103 | st.m1.take("rnd_wave", Box::new(wave)); 104 | st.bufs.insert("rnd_wave".to_string(), VecDeque::new()); 105 | 106 | toggle_modulator("rnd_wave", st); // start it off 107 | 108 | // Make a goal follower using a spring, use to modulate the amp of the sin wave 109 | let mut amp_mod = Box::new(ScalarGoalFollower::new(Box::new(ScalarSpring::new( 110 | 1.0, 0.0, 1.0, 111 | )))); 112 | amp_mod.regions.push([0.0, 12.0]); 113 | st.m2.take("amp_mod", amp_mod); // modulates amp of waveform 114 | 115 | // Make a goal follower using a spring 116 | let mut fol = Box::new(ScalarGoalFollower::new(Box::new(ScalarSpring::new( 117 | 0.3, 5.0, 0.0, 118 | )))); 119 | 120 | fol.regions.push([-3.0, -2.0]); 121 | fol.regions.push([-1.0, 1.0]); 122 | fol.regions.push([2.0, 3.0]); 123 | 124 | fol.random_region = true; 125 | fol.pause_range = [0, 100_000]; // short pause between goals 126 | 127 | st.m1.take("follow_s", fol); 128 | st.bufs.insert("follow_s".to_string(), VecDeque::new()); 129 | 130 | toggle_modulator("follow_s", st); // start it off 131 | 132 | // Make a follower using a Newtonian 133 | let mut fol = Box::new(ScalarGoalFollower::new(Box::new(Newtonian::new( 134 | [2.0, 12.0], 135 | [4.0, 24.0], 136 | [4.0, 24.0], 137 | 0.0, 138 | )))); 139 | 140 | fol.regions.push([-3.0, 3.0]); 141 | 142 | st.m1.take("follow_n", fol); 143 | st.bufs.insert("follow_n".to_string(), VecDeque::new()); 144 | 145 | toggle_modulator("follow_n", st); // start it off 146 | 147 | // Make a shift register modulator 148 | let mut sreg = ShiftRegister::new(6, [-3.0, 3.0], 0.05, 1.0, ShiftRegisterInterp::None); 149 | sreg.age_range = [5, 30]; 150 | 151 | st.m1.take("shift_rg", Box::new(sreg)); 152 | st.bufs.insert("shift_rg".to_string(), VecDeque::new()); 153 | 154 | toggle_modulator("shift_rg", st); // start it off 155 | 156 | // One more follower with spring for title animation 157 | let mut fol = Box::new(ScalarGoalFollower::new(Box::new(ScalarSpring::new( 158 | 0.4, 5.0, 0.0, 159 | )))); 160 | fol.regions.push([-8.0, 8.0]); 161 | 162 | st.m2.take("follow", fol); 163 | 164 | // Bunch of followers with springs over 0.0/1.0 165 | for i in 1..=7 { 166 | let mut f = Box::new(ScalarGoalFollower::new(Box::new(ScalarSpring::new( 167 | 1.0, 168 | 0.0, 169 | rand::thread_rng().gen_range(0.0, 1.0), 170 | )))); 171 | 172 | f.regions.push([0.0, 1.0]); 173 | f.pause_range = [0, 1000000]; 174 | 175 | st.m2.take(&format!("follow{}", i), f); 176 | } 177 | 178 | // One more wave modulator 179 | let wave = Wave::new(1.0, 0.3).wave(Box::new(|w, t| { 180 | (t * w.frequency * f32::consts::PI * 2.0).sin() * w.amplitude 181 | })); 182 | st.m2.take("waveform2", Box::new(wave)); 183 | 184 | // Populate m3 with followers that pursue normalized coordinates 185 | for i in 0..8 { 186 | let mut f = Box::new(ScalarGoalFollower::new(Box::new(Newtonian::new( 187 | [2.0, 10.0], 188 | [2.0, 12.0], 189 | [2.0, 12.0], 190 | rand::thread_rng().gen_range(0.2, 0.8), 191 | )))); 192 | 193 | f.regions.push([0.0, 1.0]); 194 | f.pause_range = [0, 1000000]; 195 | 196 | st.m3.take(&format!("follow{}", i), f); 197 | } 198 | } 199 | 200 | /// Set the current shape for the wave modulator 201 | fn set_wave_shape(shape: WaveShape, st: &mut StateData) { 202 | let md = if let Some(sw) = st.m1.get_mut("waveform") { 203 | if let Some(ss) = sw.as_any().downcast_mut::() { 204 | ss 205 | } else { 206 | return; 207 | } 208 | } else { 209 | return; 210 | }; 211 | 212 | st.shape = shape; 213 | match st.shape { 214 | WaveShape::Sine => { 215 | md.set_enabled(true); 216 | md.set_wave(Box::new(|w, t| { 217 | (t * w.frequency * f32::consts::PI * 2.0).sin() * w.amplitude 218 | })); 219 | } 220 | WaveShape::Triangle => { 221 | md.set_enabled(true); 222 | md.set_wave(Box::new(|w, t| { 223 | let t_p = t * w.frequency; 224 | (2.0 * f32::abs(2.0 * (t_p - f32::floor(t_p + 0.5))) - 1.0) * w.amplitude 225 | })); 226 | } 227 | WaveShape::Square => { 228 | md.set_enabled(true); 229 | md.set_wave(Box::new(|w, t| { 230 | f32::signum((t * w.frequency * f32::consts::PI * 2.0).sin()) * w.amplitude 231 | })); 232 | } 233 | WaveShape::Saw => { 234 | md.set_enabled(true); 235 | md.set_wave(Box::new(|w, t| { 236 | let t_p = t * w.frequency; 237 | (2.0 * (t_p - f32::floor(t_p + 0.5))) * w.amplitude 238 | })); 239 | } 240 | WaveShape::None => { 241 | md.set_enabled(false); 242 | md.set_wave(Box::new(|_, _| 0.0)); 243 | } 244 | } 245 | } 246 | 247 | /// Cycle the closure/activation state used for the wave modulator 248 | fn cycle_wave_shape(st: &mut StateData) { 249 | match st.shape { 250 | WaveShape::Sine => set_wave_shape(WaveShape::Triangle, st), 251 | WaveShape::Triangle => set_wave_shape(WaveShape::Square, st), 252 | WaveShape::Square => set_wave_shape(WaveShape::Saw, st), 253 | WaveShape::Saw => set_wave_shape(WaveShape::None, st), 254 | WaveShape::None => set_wave_shape(WaveShape::Sine, st), 255 | } 256 | } 257 | 258 | /// Cycle the follow spring undmping mode and activation 259 | fn cycle_follow_spring(key: &str, st: &mut StateData) { 260 | if let Some(sw) = st.m1.get_mut(key) { 261 | if let Some(ss) = sw.as_any().downcast_mut::() { 262 | let mut enabled = ss.enabled(); 263 | 264 | if enabled { 265 | if let Some(sp) = ss.follower.as_any().downcast_mut::() { 266 | if sp.undamp == 0.0 { 267 | enabled = false; 268 | sp.undamp = 5.0; 269 | } else { 270 | sp.undamp = 0.0; 271 | } 272 | } 273 | } else { 274 | enabled = true; 275 | } 276 | 277 | ss.set_enabled(enabled); 278 | } 279 | } 280 | } 281 | 282 | /// Cycle the closure/activation state used for the wave modulator 283 | fn cycle_shift_reg_interpolation(key: &str, st: &mut StateData) { 284 | if let Some(sw) = st.m1.get_mut(key) { 285 | if let Some(ss) = sw.as_any().downcast_mut::() { 286 | if ss.enabled() { 287 | match ss.interp { 288 | ShiftRegisterInterp::Quadratic => ss.set_enabled(false), 289 | ShiftRegisterInterp::Linear => ss.interp = ShiftRegisterInterp::Quadratic, 290 | ShiftRegisterInterp::None => ss.interp = ShiftRegisterInterp::Linear, 291 | } 292 | } else { 293 | ss.set_enabled(true); 294 | ss.interp = ShiftRegisterInterp::None; 295 | } 296 | } 297 | } 298 | } 299 | 300 | /// Toggle activation status for the given modulator 301 | fn toggle_modulator(key: &str, st: &mut StateData) { 302 | let m = if let Some(s) = st.m1.get_mut(key) { 303 | s 304 | } else if let Some(s) = st.m2.get_mut(key) { 305 | s 306 | } else { 307 | return; 308 | }; 309 | 310 | let state = m.enabled(); 311 | m.set_enabled(!state); 312 | } 313 | 314 | /// Try to get a ref to the give modulator - searches in m1 first, m2 if not found 315 | fn get_modulator<'a>( 316 | key: &str, 317 | st: &'a StateData, 318 | ) -> Option<&'a Box>> { 319 | return if let Some(s) = st.m1.get(key) { 320 | Some(s) 321 | } else if let Some(s) = st.m2.get(key) { 322 | Some(s) 323 | } else { 324 | None 325 | }; 326 | } 327 | 328 | /// ### 329 | /// ### Menus 330 | /// ### 331 | 332 | enum MenuPages { 333 | MainMenu, 334 | 335 | Modulators, 336 | Primitives, 337 | CubicCurves, 338 | } 339 | 340 | /// Process and dispatch the current menu 341 | fn dispatch(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 342 | match st.menu_page { 343 | MenuPages::MainMenu => main_menu(st, d, c, w), 344 | 345 | MenuPages::Modulators => vis_mod_waves(st, d, c, w), 346 | MenuPages::Primitives => primitives(st, d, c, w), 347 | MenuPages::CubicCurves => cubic_curves(st, d, c, w), 348 | } 349 | 350 | match st.entered_text.as_ref() { 351 | " " => { 352 | st.paused = !st.paused; 353 | st.entered_text = String::new(); 354 | } 355 | _ => (), 356 | } 357 | 358 | if st.paused { 359 | let at = vec2(d.dims[0] as f32 - 148.0, d.dims[1] as f32 - 18.0); 360 | draw_text("[PAUSED]", at, d.theme[9], 24, d, c, w); 361 | } 362 | } 363 | 364 | /// Draw a menu display 365 | fn draw_menu( 366 | menu: &[String], 367 | col: types::Color, 368 | d: &mut DrawData, 369 | c: &Context, 370 | w: &mut PistonWindow, 371 | ) { 372 | let mut at = vec2(16.0, 32.0); 373 | for m in menu { 374 | draw_text(m, at, col, 16, d, c, w); 375 | at.y += 20.0; 376 | } 377 | } 378 | 379 | /// Draw a menu display 380 | fn draw_info( 381 | info: &[String], 382 | col: types::Color, 383 | d: &mut DrawData, 384 | c: &Context, 385 | w: &mut PistonWindow, 386 | ) { 387 | let n = info.len(); 388 | let mut at = vec2(16.0, d.dims[1] as f32 - n as f32 * 18.0); 389 | 390 | for m in info { 391 | draw_text(m, at, col, 14, d, c, w); 392 | at.y += 18.0; 393 | } 394 | } 395 | 396 | /// Main Menu 397 | fn main_menu(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 398 | main_menu_exe(st, d, c, w); 399 | 400 | // Draw the menu 401 | let menu = [ 402 | "MODULATOR PLAY - app for Modulator trait, extended primitives testing".to_string(), 403 | "".to_string(), 404 | "[ 1 ] Modulators".to_string(), 405 | "[ 2 ] Primitives".to_string(), 406 | "[ 3 ] Cubic Curves".to_string(), 407 | ]; 408 | draw_menu(&menu, d.theme[1], d, c, w); 409 | 410 | let info = [ 411 | "By Andrea Pessino & Jason Hise".to_string(), 412 | "Copyright © 2018 Ready At Dawn Studios".to_string(), 413 | ]; 414 | draw_info(&info, d.theme[6], d, c, w); 415 | 416 | // Process the menu 417 | match st.entered_text.as_ref() { 418 | "1" => { 419 | st.menu_page = MenuPages::Modulators; 420 | st.entered_text = String::new(); 421 | } 422 | "2" => { 423 | st.menu_page = MenuPages::Primitives; 424 | st.entered_text = String::new(); 425 | } 426 | "3" => { 427 | st.menu_page = MenuPages::CubicCurves; 428 | st.entered_text = String::new(); 429 | } 430 | _ => (), 431 | } 432 | } 433 | 434 | fn main_menu_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 435 | penta_dance(st, d, c, w); 436 | tri_dance(st, d, c, w); 437 | } 438 | 439 | /// Visualize some of the modulator waves 440 | fn vis_mod_waves(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 441 | vis_mod_waves_exe(st, d, c, w); 442 | 443 | // Draw the menu 444 | let menu = [ 445 | "MODULATORS - demo of types implementing the Modulator trait".to_string(), 446 | "".to_string(), 447 | "[ 1 ] Wave: sin > tri > square > saw > off".to_string(), 448 | "[ 2 ] Follow|Spring: undamped > damped > off".to_string(), 449 | "[ 3 ] Follow|Newtonian: on > off".to_string(), 450 | "[ 4 ] Wave|Random Walk: on > off".to_string(), 451 | "[ 5 ] Shift Register: near > lin > quad > off".to_string(), 452 | "".to_string(), 453 | "[ 0 ] Back".to_string(), 454 | ]; 455 | draw_menu(&menu, d.theme[1], d, c, w); 456 | 457 | // Process the menu 458 | match st.entered_text.as_ref() { 459 | "0" => { 460 | st.menu_page = MenuPages::MainMenu; 461 | st.entered_text = String::new(); 462 | } 463 | "1" => { 464 | cycle_wave_shape(st); 465 | st.entered_text = String::new(); 466 | } 467 | "2" => { 468 | cycle_follow_spring("follow_s", st); 469 | st.entered_text = String::new(); 470 | } 471 | "3" => { 472 | toggle_modulator("follow_n", st); 473 | st.entered_text = String::new(); 474 | } 475 | "4" => { 476 | toggle_modulator("rnd_wave", st); 477 | st.entered_text = String::new(); 478 | } 479 | "5" => { 480 | cycle_shift_reg_interpolation("shift_rg", st); 481 | st.entered_text = String::new(); 482 | } 483 | _ => (), 484 | } 485 | } 486 | 487 | fn vis_mod_waves_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 488 | draw_ruler(d, c, w); 489 | 490 | let mut ord = 0; 491 | 492 | draw_modulator("waveform", &mut ord, 1, st, d, c, w); 493 | draw_modulator("follow_s", &mut ord, 9, st, d, c, w); 494 | draw_modulator("follow_n", &mut ord, 2, st, d, c, w); 495 | draw_modulator("rnd_wave", &mut ord, 10, st, d, c, w); 496 | draw_modulator("shift_rg", &mut ord, 6, st, d, c, w); 497 | } 498 | 499 | /// Testing of prim drawing, lin e segments 500 | fn primitives(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 501 | primitives_exe(st, d, c, w); 502 | 503 | // Draw the menu 504 | let menu = [ 505 | "PRIMITIVES - special prims provided by prims.rs".to_string(), 506 | "".to_string(), 507 | "[ 1 ] Toggle n-gons filled".to_string(), 508 | "[ 2 ] Toggle connectors".to_string(), 509 | "[ 3 ] Toggle debug quads".to_string(), 510 | "[ 4 ] Toggle debug wireframe".to_string(), 511 | "".to_string(), 512 | "[ 0 ] Back".to_string(), 513 | ]; 514 | draw_menu(&menu, d.theme[1], d, c, w); 515 | 516 | // Process the menu 517 | match st.entered_text.as_ref() { 518 | "1" => { 519 | d.prim_filled = !d.prim_filled; 520 | st.entered_text = String::new(); 521 | } 522 | "2" => { 523 | d.prim_no_connect = !d.prim_no_connect; 524 | st.entered_text = String::new(); 525 | } 526 | "3" => { 527 | d.prim_face_debug = !d.prim_face_debug; 528 | st.entered_text = String::new(); 529 | } 530 | "4" => { 531 | d.prim_debug = !d.prim_debug; 532 | st.entered_text = String::new(); 533 | } 534 | "0" => { 535 | st.menu_page = MenuPages::MainMenu; 536 | st.entered_text = String::new(); 537 | } 538 | _ => (), 539 | } 540 | } 541 | 542 | fn primitives_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 543 | single_lines_exe(st, d, c, w); 544 | complex_sweeps_exe(st, d, c, w); 545 | n_gons_exe(st, d, c, w); 546 | } 547 | 548 | fn single_lines_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 549 | let mut lp = prims::DrawParms::new(); 550 | 551 | let rot = st.m2.value("follow") * 0.4; 552 | let rst = f32::consts::PI / 3.0; 553 | 554 | let center = vec2(450.0, 600.0); 555 | let halfl = 500.0 * 0.5; 556 | 557 | // First line 558 | lp.color = [d.theme[8], d.theme[6]]; 559 | lp.thickness = [6.0, 42.0]; 560 | lp.cap = [prims::CapStyle::Round(16), prims::CapStyle::Round(32)]; 561 | 562 | let (sn, cs) = rot.sin_cos(); 563 | let dir = vec2(cs - sn, cs + sn); 564 | let l0 = [ 565 | vec2(center.x - dir.x * halfl, center.y - dir.y * halfl), 566 | vec2(center.x + dir.x * halfl, center.y + dir.y * halfl), 567 | ]; 568 | d.prim_context_debug(w).draw_line(l0, &lp, c); 569 | 570 | // Second line 571 | lp.color = [d.theme[1], d.theme[2]]; 572 | lp.color[0][3] = 0.5; 573 | lp.color[1][3] = 0.5; 574 | lp.thickness = [20.0, (st.m2.value("waveform2") * 18.0).abs()]; 575 | lp.cap = [prims::CapStyle::Square(0.0), prims::CapStyle::Round(32)]; 576 | 577 | let (sn, cs) = (rot + rst).sin_cos(); 578 | let dir = vec2(cs - sn, cs + sn); 579 | let l1 = [ 580 | vec2(center.x - dir.x * halfl, center.y - dir.y * halfl), 581 | vec2(center.x + dir.x * halfl, center.y + dir.y * halfl), 582 | ]; 583 | d.prim_context_debug(w).draw_line(l1, &lp, c); 584 | 585 | // Third line 586 | lp.color = [d.theme[9], d.theme[11]]; 587 | lp.color[0][3] = 0.5; 588 | lp.color[1][3] = 0.5; 589 | lp.cap = [prims::CapStyle::Round(16), prims::CapStyle::Square(0.0)]; 590 | lp.thickness = [st.m2.value("follow4") * 60.0, st.m2.value("follow3") * 60.0]; 591 | 592 | let (sn, cs) = (rot + rst * 2.0).sin_cos(); 593 | let dir = vec2(cs - sn, cs + sn); 594 | let l1 = [ 595 | vec2(center.x - dir.x * halfl, center.y - dir.y * halfl), 596 | vec2(center.x + dir.x * halfl, center.y + dir.y * halfl), 597 | ]; 598 | d.prim_context_debug(w).draw_line(l1, &lp, c); 599 | } 600 | 601 | fn complex_sweeps_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 602 | let mut lp0 = prims::DrawParms::new(); 603 | 604 | lp0.thickness = [30.0, 30.0]; 605 | if d.prim_no_connect { 606 | lp0.connect_subs = 0; 607 | } 608 | 609 | let o0 = vec2(1300.0, 280.0); 610 | let o1 = o0; 611 | 612 | let r = st.m2.elapsed_us("waveform2") as f32 / 1_000_000 as f32; 613 | 614 | let r0 = cgmath::Matrix2::from_angle(Rad(r * 0.3)); 615 | let r1 = cgmath::Matrix2::from_angle(Rad(r * -0.6)); 616 | 617 | lp0.thickness[0] = 30.0 + st.m2.value("follow").abs() * 4.0; 618 | lp0.thickness[1] = 30.0; 619 | 620 | let line0 = [ 621 | o0 + r0 * (vec2(o0[0] - 280.0, o0[1]) - o0), 622 | o0 + r0 * (vec2(o0[0] - 80.0, o0[1]) - o0), 623 | ]; 624 | let line1 = [ 625 | o1 + r1 * (vec2(o0[0], o0[1] + 80.0) - o1), 626 | o1 + r1 * (vec2(o0[0], o0[1] + 400.0) - o1), 627 | ]; 628 | 629 | lp0.color = [d.theme[2], d.theme[6]]; 630 | let lp1 = prims::DrawParms { 631 | thickness: [30.0, 30.0 + st.m2.value("waveform2") * 3.0], 632 | color: [d.theme[3], d.theme[7]], 633 | connect_subs: 0, 634 | ..lp0 635 | }; 636 | 637 | d.prim_context_debug(w) 638 | .draw_lines(&[line0, line1], &[lp0, lp1], &c); 639 | } 640 | 641 | fn n_gons_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 642 | let dp = prims::DrawParms::new() 643 | .color([d.theme[1], d.theme[9]]) 644 | .thickness([12.0, 12.0]) 645 | .connect_subs(if d.prim_no_connect { 0 } else { 16 }); 646 | let trs = Dec2 { 647 | scale: 160.0 + st.m2.value("follow2") * 16.0, 648 | disp: vec2(1180.0, 860.0), 649 | rot: Rotation2::from_angle(Rad(st.m2.value("follow1") * 30.0)), 650 | }; 651 | 652 | let (lines, parms) = prims::n_gon(4, st.m2.value("follow3"), dp, &trs, None); 653 | if d.prim_filled { 654 | d.prim_context_debug(w) 655 | .draw_lines_filled(&lines, &parms, &c); 656 | } else { 657 | d.prim_context_debug(w).draw_lines(&lines, &parms, &c); 658 | } 659 | 660 | let dp = prims::DrawParms::new() 661 | .color([d.theme[7], d.theme[10]]) 662 | .thickness([ 663 | 4.0 + st.m2.value("follow4") * 12.0, 664 | 4.0 + st.m2.value("follow5") * 12.0, 665 | ]).connect_subs(if d.prim_no_connect { 0 } else { 16 }); 666 | let trs = Dec2 { 667 | scale: 150.0, 668 | disp: vec2(1600.0, 860.0), 669 | rot: Rotation2::from_angle(Rad(st.m2.value("follow6") * 30.0)), 670 | }; 671 | 672 | let (lines, parms) = prims::n_gon(7, st.m2.value("follow7"), dp, &trs, None); 673 | if d.prim_filled { 674 | d.prim_context_debug(w) 675 | .draw_lines_filled(&lines, &parms, &c); 676 | } else { 677 | d.prim_context_debug(w).draw_lines(&lines, &parms, &c); 678 | } 679 | } 680 | 681 | /// Generate a 5-gon primitive and then animate its parameters 682 | fn penta_dance(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 683 | let c0 = 1.0 + st.m2.value("follow4") * (d.theme.len() - 1) as f32; 684 | let c1 = 1.0 + st.m2.value("follow5") * (d.theme.len() - 1) as f32; 685 | 686 | let dp = prims::DrawParms::new() 687 | .color([blended_theme_color(c0, d), blended_theme_color(c1, d)]) 688 | .thickness([ 689 | 2.0 + 30.0 * st.m2.value("follow2"), 690 | 2.0 + 30.0 * st.m2.value("follow3"), 691 | ]).connect_subs(16); 692 | let trs = Dec2 { 693 | scale: 200.0 * st.m2.value("waveform2").abs() * 0.5, 694 | disp: vec2(d.hdims[0] as f32, d.hdims[1] as f32), 695 | rot: Rotation2::from_angle(Rad(st.m2.value("follow"))), 696 | }; 697 | 698 | let (lines, parms) = prims::n_gon(5, 0.85 * st.m2.value("follow1"), dp, &trs, None); 699 | 700 | let mut cd = d.prim_context(w); 701 | cd.draw_lines_filled(&lines, &parms, &c); 702 | } 703 | 704 | /// Generate a 3-gon primitive and then animate its parameters 705 | fn tri_dance(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 706 | let c0 = 1.0 + st.m2.value("follow1") * (d.theme.len() - 1) as f32; 707 | let c1 = 1.0 + st.m2.value("follow2") * (d.theme.len() - 1) as f32; 708 | 709 | let dp = prims::DrawParms::new() 710 | .color([blended_theme_color(c0, d), blended_theme_color(c1, d)]) 711 | .thickness([ 712 | 2.0 + 30.0 * st.m2.value("follow3"), 713 | 2.0 + 30.0 * st.m2.value("follow4"), 714 | ]).connect_subs(16); 715 | let trs = Dec2 { 716 | scale: 200.0 * st.m2.value("follow") * 0.5, 717 | disp: vec2(d.hdims[0] as f32, d.hdims[1] as f32), 718 | rot: Rotation2::from_angle(Rad(st.m2.value("waveform2"))), 719 | }; 720 | 721 | let (lines, parms) = prims::n_gon(3, 0.85 * st.m2.value("follow5"), dp, &trs, None); 722 | 723 | let mut cd = d.prim_context(w); 724 | cd.draw_lines(&lines, &parms, &c); 725 | } 726 | 727 | /// Testing cubic curves 728 | struct CubicCurvesData { 729 | c0: CubicCurve, // curve being drawn 730 | 731 | tracking: bool, // the curve is being tracked 732 | tracking_at: prims::CurveQueryResult, // cached query result when we started tracking 733 | tracking_ct: [Vec2; 4], // snapshot of controls when tracking started 734 | 735 | thick_choices: Vec<[f32; 2]>, 736 | thick_choice: usize, 737 | 738 | connectors: u16, 739 | } 740 | impl CubicCurvesData { 741 | fn new() -> Self { 742 | let mut c0 = CubicCurve::new(); 743 | 744 | c0.set_control_points(&[ 745 | vec2(286.0, 729.0), 746 | vec2(598.0, 925.0), 747 | vec2(308.0, 198.0), 748 | vec2(1232.0, 462.0), 749 | ]); 750 | 751 | CubicCurvesData { 752 | c0, 753 | tracking: false, 754 | tracking_at: prims::CurveQueryResult::None, 755 | tracking_ct: [vec2(0.0, 0.0); 4], 756 | 757 | thick_choices: vec![[12.0, 12.0], [32.0, 32.0], [6.0, 48.0], [3.0, 3.0]], 758 | thick_choice: 0, 759 | 760 | connectors: 4, 761 | } 762 | } 763 | } 764 | 765 | fn cubic_curves(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 766 | cubic_curves_exe(st, d, c, w); 767 | 768 | // Draw the menu 769 | let menu = [ 770 | "CUBIC CURVES - prim rendered cubic curves with Shemanarev metrics".to_string(), 771 | "".to_string(), 772 | "[ 1 ] Cycle thickness".to_string(), 773 | "[ 2 ] Toggle connectors".to_string(), 774 | "[ 3 ] Toggle animation".to_string(), 775 | "".to_string(), 776 | "[ 0 ] Back".to_string(), 777 | ]; 778 | draw_menu(&menu, d.theme[1], d, c, w); 779 | 780 | // Process the menu 781 | match st.entered_text.as_ref() { 782 | "0" => { 783 | st.menu_page = MenuPages::MainMenu; 784 | st.entered_text = String::new(); 785 | } 786 | "1" => { 787 | cycle_curve_thickness(st); 788 | st.entered_text = String::new(); 789 | } 790 | "2" => { 791 | toggle_curve_connectors(st); 792 | st.entered_text = String::new(); 793 | } 794 | "3" => { 795 | st.curve_animate = !st.curve_animate; 796 | st.entered_text = String::new(); 797 | } 798 | _ => (), 799 | } 800 | } 801 | 802 | /// Cycle the closure/activation state used for the wave modulator 803 | fn cycle_curve_thickness(st: &mut StateData) { 804 | let n = st.curve_data.thick_choices.len(); 805 | st.curve_data.thick_choice = if st.curve_data.thick_choice < n - 1 { 806 | st.curve_data.thick_choice + 1 807 | } else { 808 | 0 809 | }; 810 | } 811 | 812 | /// Show/hide the curve sample segment connectors 813 | fn toggle_curve_connectors(st: &mut StateData) { 814 | if st.curve_data.connectors == 0 { 815 | st.curve_data.connectors = 4; 816 | } else { 817 | st.curve_data.connectors = 0; 818 | } 819 | } 820 | 821 | fn cubic_curves_exe(st: &mut StateData, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 822 | let mut dp = prims::CurveDrawParms::new() 823 | .parms( 824 | prims::DrawParms::new() 825 | .color([d.theme[5], d.theme[1]]) 826 | .thickness(st.curve_data.thick_choices[st.curve_data.thick_choice]) 827 | .cap([prims::CapStyle::Round(16), prims::CapStyle::Round(16)]) 828 | .connect_subs(st.curve_data.connectors), 829 | ).extra_features(true) 830 | .segment_scale(0.8); 831 | 832 | let (mouse_pos, cq) = curve_tracking(st, d, &dp); 833 | if st.curve_animate { 834 | curve_animation(st, &mut dp, d); 835 | } 836 | 837 | d.prim_context(w) 838 | .draw_curve(&mut st.curve_data.c0, &dp, mouse_pos, &cq, None, &c); 839 | 840 | // If enabled, draw some info text 841 | if dp.feature_info { 842 | let mut tx = String::new(); 843 | 844 | match cq { 845 | prims::CurveQueryResult::Sample { 846 | k, 847 | pt: _, 848 | time, 849 | distance: _, 850 | } => tx = format!("sample: {:?} at: {:.2}", k, time), 851 | prims::CurveQueryResult::Handle { 852 | k: _, 853 | pt: _, 854 | time: _, 855 | distance: _, 856 | } => (), 857 | prims::CurveQueryResult::Control { k, pt, distance: _ } => { 858 | tx = format!("control: {:?} loc: {:.2}, {:.2}", k, pt.x, pt.y) 859 | } 860 | prims::CurveQueryResult::None => { 861 | if st.curve_data.c0.in_bounds(mouse_pos, dp.sample_radius) { 862 | tx = format!( 863 | "samples: {:?} length: {:.2}", 864 | st.curve_data.c0.samples().len(), 865 | st.curve_data.c0.length() 866 | ); 867 | } 868 | } 869 | } 870 | 871 | if tx.len() > 0 { 872 | let tc = dp.selecc.mul_rgba(1.0, 1.0, 1.0, 0.5 as f32); 873 | let at = mouse_pos + vec2(24.0, 24.0); 874 | 875 | draw_text(&tx, at, tc, 12, d, c, w); 876 | } 877 | } 878 | } 879 | 880 | /// Manage the tracking/interaction of the curve 881 | fn curve_tracking( 882 | st: &mut StateData, 883 | d: &mut DrawData, 884 | dp: &prims::CurveDrawParms, 885 | ) -> (Vec2, prims::CurveQueryResult) { 886 | let mut mouse_pos = d.mouse_pos; // begin with the current mouse position 887 | let mut cq = dp.find(&mut st.curve_data.c0, mouse_pos); 888 | 889 | let td = mouse_pos - st.tracking_from; // tracking offset 890 | 891 | if st.tracking == false { 892 | st.curve_data.tracking = false; 893 | st.curve_data.tracking_at = prims::CurveQueryResult::None; 894 | } else if st.curve_data.tracking == false { 895 | st.curve_data.tracking = true; 896 | st.curve_data.tracking_at = cq; 897 | st.curve_data.tracking_ct = st.curve_data.c0.control_points().clone(); // starting controls 898 | } 899 | 900 | if st.curve_data.tracking { 901 | match st.curve_data.tracking_at { 902 | prims::CurveQueryResult::Sample { 903 | k, 904 | pt, 905 | time, 906 | distance, 907 | } => { 908 | let cs = [ 909 | st.curve_data.tracking_ct[0] + td, 910 | st.curve_data.tracking_ct[1] + td, 911 | st.curve_data.tracking_ct[2] + td, 912 | st.curve_data.tracking_ct[3] + td, 913 | ]; 914 | st.curve_data.c0.set_control_points(&cs); 915 | cq = prims::CurveQueryResult::Sample { 916 | k, 917 | pt: pt + td, 918 | time, 919 | distance, 920 | }; 921 | } 922 | prims::CurveQueryResult::Handle { 923 | k, 924 | pt, 925 | time, 926 | distance, 927 | } => { 928 | let cs = [ 929 | if k == 0 { 930 | st.curve_data.tracking_ct[0] + td 931 | } else { 932 | st.curve_data.tracking_ct[0] 933 | }, 934 | if k == 0 { 935 | st.curve_data.tracking_ct[1] + td 936 | } else { 937 | st.curve_data.tracking_ct[1] 938 | }, 939 | if k == 1 { 940 | st.curve_data.tracking_ct[2] + td 941 | } else { 942 | st.curve_data.tracking_ct[2] 943 | }, 944 | if k == 1 { 945 | st.curve_data.tracking_ct[3] + td 946 | } else { 947 | st.curve_data.tracking_ct[3] 948 | }, 949 | ]; 950 | st.curve_data.c0.set_control_points(&cs); 951 | cq = prims::CurveQueryResult::Handle { 952 | k, 953 | pt: pt + td, 954 | time, 955 | distance, 956 | }; 957 | } 958 | prims::CurveQueryResult::Control { k, pt, distance } => { 959 | let cs = [ 960 | if k == 0 { 961 | st.curve_data.tracking_ct[0] + td 962 | } else { 963 | st.curve_data.tracking_ct[0] 964 | }, 965 | if k == 1 { 966 | st.curve_data.tracking_ct[1] + td 967 | } else { 968 | st.curve_data.tracking_ct[1] 969 | }, 970 | if k == 2 { 971 | st.curve_data.tracking_ct[2] + td 972 | } else { 973 | st.curve_data.tracking_ct[2] 974 | }, 975 | if k == 3 { 976 | st.curve_data.tracking_ct[3] + td 977 | } else { 978 | st.curve_data.tracking_ct[3] 979 | }, 980 | ]; 981 | st.curve_data.c0.set_control_points(&cs); 982 | cq = prims::CurveQueryResult::Control { 983 | k, 984 | pt: pt + td, 985 | distance, 986 | }; 987 | } 988 | prims::CurveQueryResult::None => { 989 | mouse_pos = st.tracking_from; 990 | cq = prims::CurveQueryResult::None; 991 | } 992 | } 993 | } 994 | 995 | (mouse_pos, cq) 996 | } 997 | 998 | /// Animate the curve with modulators 999 | fn curve_animation(st: &mut StateData, dp: &mut prims::CurveDrawParms, d: &mut DrawData) { 1000 | let x = d.dims[0] as f32; 1001 | let y = d.dims[1] as f32; 1002 | 1003 | let cs = [ 1004 | vec2(x * st.m3.value("follow0"), y * st.m3.value("follow1")), 1005 | vec2(x * st.m3.value("follow2"), y * st.m3.value("follow3")), 1006 | vec2(x * st.m3.value("follow4"), y * st.m3.value("follow5")), 1007 | vec2(x * st.m3.value("follow6"), y * st.m3.value("follow7")), 1008 | ]; 1009 | 1010 | st.curve_data.c0.set_control_points(&cs); 1011 | 1012 | dp.samples = false; // not while animating 1013 | dp.bounds = false; 1014 | dp.feature_info = false; 1015 | 1016 | let c0 = 1.0 + st.m2.value("follow4") * (d.theme.len() - 1) as f32; 1017 | let c1 = 1.0 + st.m2.value("follow5") * (d.theme.len() - 1) as f32; 1018 | 1019 | dp.parms.color = [blended_theme_color(c0, d), blended_theme_color(c1, d)]; 1020 | } 1021 | 1022 | /// ### 1023 | /// ### Build and drive the application state 1024 | /// ### 1025 | 1026 | enum WaveShape { 1027 | Sine, 1028 | Triangle, 1029 | Square, 1030 | Saw, 1031 | None, 1032 | } 1033 | 1034 | /// Container for application state data 1035 | struct StateData { 1036 | menu_page: MenuPages, 1037 | paused: bool, 1038 | 1039 | m1: ModulatorEnv, 1040 | m2: ModulatorEnv, 1041 | m3: ModulatorEnv, // dedicated to animating the curve controls 1042 | 1043 | buf_samples_per_sec: u32, // number of modulator samples collected per second 1044 | buf_max_fade_time: f32, // max seconds to fade out the buffer 1045 | 1046 | buf_scroll_vel: f32, // horizontal velocity of samples 1047 | 1048 | bufs: HashMap>, // modulator ring buffers 1049 | sampling_dt: u64, // microseconds since the last time we captured samples 1050 | 1051 | shape: WaveShape, // current shape for the wave modulator 1052 | curve_data: CubicCurvesData, // testing curve state 1053 | 1054 | tracking: bool, // when true, the left mouse button is being held 1055 | tracking_from: Vec2, // position of the mouse pointer when the tracking started 1056 | mouse_scroll: Vec2, // mouse scrolling (wheels) 1057 | curve_animate: bool, // animate the curve with followers 1058 | 1059 | entered_text: String, // current text input 1060 | } 1061 | 1062 | impl StateData { 1063 | fn new() -> Self { 1064 | StateData { 1065 | menu_page: MenuPages::MainMenu, 1066 | paused: false, 1067 | 1068 | m1: ModulatorEnv::new(), 1069 | m2: ModulatorEnv::new(), 1070 | m3: ModulatorEnv::new(), 1071 | 1072 | buf_samples_per_sec: 30, 1073 | buf_max_fade_time: 2.0, 1074 | 1075 | buf_scroll_vel: 500.0, 1076 | 1077 | bufs: HashMap::new(), 1078 | sampling_dt: 0, 1079 | 1080 | shape: WaveShape::Sine, 1081 | curve_data: CubicCurvesData::new(), 1082 | 1083 | tracking: false, 1084 | tracking_from: vec2(f32::INFINITY, f32::INFINITY), 1085 | mouse_scroll: vec2(0.0, 0.0), 1086 | curve_animate: false, 1087 | 1088 | entered_text: String::new(), 1089 | } 1090 | } 1091 | } 1092 | 1093 | /// Container for rendering state data 1094 | struct DrawData { 1095 | theme: [types::Color; 14], // color theme 1096 | glyphs: Glyphs, // font data 1097 | 1098 | dot_rad: f64, // radius for the position dots 1099 | tak_len: f64, // lenght of the axis tacks 1100 | 1101 | px_target: f64, // target subdivision of range in pixels 1102 | px_base: f64, // log base for the grid subdivisions 1103 | 1104 | margin_scale: f32, // scale of the calculated domain, to leave some margin 1105 | 1106 | prim_no_connect: bool, // do not draw the connectors 1107 | prim_debug: bool, // enable wireframe debug rendering for prim page 1108 | prim_face_debug: bool, // enable debug face rendering for prim page 1109 | prim_filled: bool, // draw the bottom ngons filled in prim page 1110 | 1111 | domain: [f32; 2], // frame parameters 1112 | 1113 | dims: [f64; 2], 1114 | hdims: [f64; 2], 1115 | 1116 | c2d: Box>, 1117 | 1118 | mouse_pos: Vec2, // current position of the mouse pointer 1119 | } 1120 | 1121 | impl DrawData { 1122 | /// Initialize all rendering data 1123 | fn new(window: &mut PistonWindow) -> Self { 1124 | let assets = find_folder::Search::ParentsThenKids(3, 3) 1125 | .for_folder("assets") 1126 | .unwrap(); 1127 | 1128 | let theme = color_theme(); 1129 | let glyphs = Glyphs::new( 1130 | assets.join("FiraCode-Medium.ttf"), 1131 | window.factory.clone(), 1132 | TextureSettings::new(), 1133 | ).unwrap(); 1134 | 1135 | let c2d = Prims2d::new(OpenGL::V3_2, &mut window.factory); 1136 | 1137 | DrawData { 1138 | theme, 1139 | glyphs, 1140 | dot_rad: 5.0, 1141 | tak_len: 14.0, 1142 | px_target: 120.0, 1143 | px_base: 2.0, 1144 | margin_scale: 1.1, // 10% on each side 1145 | prim_no_connect: false, 1146 | prim_debug: false, 1147 | prim_face_debug: false, 1148 | prim_filled: false, 1149 | domain: [-1.0, 1.0], 1150 | dims: [0.0, 0.0], 1151 | hdims: [0.0, 0.0], 1152 | c2d: Box::new(c2d), 1153 | mouse_pos: vec2(f32::INFINITY, f32::INFINITY), 1154 | } 1155 | } 1156 | 1157 | /// Create a piston drawing context 1158 | fn draw_context<'a>( 1159 | &self, 1160 | window: &'a mut PistonWindow, 1161 | ) -> gfx_graphics::GfxGraphics<'a, gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> { 1162 | gfx_graphics::GfxGraphics::new( 1163 | &mut window.encoder, 1164 | &window.output_color, 1165 | &window.output_stencil, 1166 | &mut window.g2d, 1167 | ) 1168 | } 1169 | 1170 | /// Create a drawing context for our custom primitives 1171 | fn prim_context<'a>( 1172 | &'a mut self, 1173 | window: &'a mut PistonWindow, 1174 | ) -> PrimGraphics<'a, gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> { 1175 | PrimGraphics::new( 1176 | &mut window.encoder, 1177 | &window.output_color, 1178 | &window.output_stencil, 1179 | &mut self.c2d, 1180 | ) 1181 | } 1182 | 1183 | /// Create a custom primitive drawing context, with optional debug wireframe rendering 1184 | fn prim_context_debug<'a>( 1185 | &'a mut self, 1186 | window: &'a mut PistonWindow, 1187 | ) -> PrimGraphics<'a, gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> { 1188 | let dw = self.prim_debug; 1189 | let df = self.prim_face_debug; 1190 | let mut d = self.prim_context(window); 1191 | d.debug_wireframe = dw; 1192 | d.debug_faces = df; 1193 | d 1194 | } 1195 | } 1196 | 1197 | /// Calculate the corrected rendering coordinate given a location in the domain 1198 | fn calc_y(n: f64, d: &mut DrawData) -> [f64; 2] { 1199 | let dom = (d.domain[1] - d.domain[0]) as f64 * 0.5; 1200 | [d.hdims[0], d.hdims[1] - n / dom * d.hdims[1]] 1201 | } 1202 | fn calc_xy(n: f64, x: f64, d: &mut DrawData) -> [f64; 2] { 1203 | let xy = calc_y(n, d); 1204 | [xy[0] + x, xy[1]] 1205 | } 1206 | 1207 | /// Draw a tack with label on the y axis 1208 | fn draw_tack(n: f64, scale: f64, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 1209 | let g = &mut d.draw_context(w); 1210 | let at = calc_y(n, d); 1211 | 1212 | line::Line::new(d.theme[8], 1.0).draw( 1213 | [d.hdims[0], at[1], d.hdims[0] - d.tak_len * scale, at[1]], 1214 | &c.draw_state, 1215 | c.transform, 1216 | g, 1217 | ); 1218 | 1219 | let tx = format!("{:.1}", n); 1220 | let tw = d.glyphs.width(12, &tx).unwrap(); 1221 | let transform = c 1222 | .transform 1223 | .trans(at[0] - d.tak_len * scale - tw - 6.0, at[1] + d.dot_rad); 1224 | text::Text::new_color(d.theme[7].mul_rgba(1.0, 1.0, 1.0, scale as f32), 12) 1225 | .draw(&tx, &mut d.glyphs, &c.draw_state, transform, g) 1226 | .unwrap(); 1227 | } 1228 | 1229 | /// Draw a positon dot with label on the y axis 1230 | fn draw_dot(n: f64, col: usize, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 1231 | let at = calc_y(n, d); 1232 | 1233 | ellipse::Ellipse::new(d.theme[col]).draw( 1234 | [ 1235 | at[0] - d.dot_rad, 1236 | at[1] - d.dot_rad, 1237 | d.dot_rad * 2.0, 1238 | d.dot_rad * 2.0, 1239 | ], 1240 | &c.draw_state, 1241 | c.transform, 1242 | &mut d.draw_context(w), 1243 | ); 1244 | 1245 | draw_label(n, col, d, c, w); 1246 | } 1247 | 1248 | /// Draw a positon dot with label on the y axis 1249 | fn draw_label(n: f64, col: usize, d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 1250 | let g = &mut d.draw_context(w); 1251 | let at = calc_y(n, d); 1252 | 1253 | let transform = c 1254 | .transform 1255 | .trans(at[0] + d.dot_rad + 6.0, at[1] + d.dot_rad); 1256 | text::Text::new_color(d.theme[col], 12) 1257 | .draw( 1258 | &format!("{:+.3}", n), 1259 | &mut d.glyphs, 1260 | &c.draw_state, 1261 | transform, 1262 | g, 1263 | ).unwrap(); 1264 | } 1265 | 1266 | /// Draw a domain bracket 1267 | fn draw_bracket( 1268 | key: &str, 1269 | val: f32, 1270 | gol: f32, 1271 | dom: [f32; 2], 1272 | ord: usize, 1273 | col: usize, 1274 | d: &mut DrawData, 1275 | c: &Context, 1276 | w: &mut PistonWindow, 1277 | ) { 1278 | let xvalue = 82.0; 1279 | let xbase = xvalue + 64.0 * ord as f64; 1280 | let xoffset = 64.0; 1281 | 1282 | let top = calc_xy(dom[1] as f64, xbase, d); 1283 | let bot = calc_xy(dom[0] as f64, xbase, d); 1284 | 1285 | let val = calc_xy(val as f64, xvalue, d); 1286 | let gol = calc_xy(gol as f64, xbase + xoffset, d); 1287 | 1288 | let col = d.theme[col].mul_rgba(1.0, 1.0, 1.0, 0.5 as f32); 1289 | 1290 | { 1291 | let g = &mut d.draw_context(w); 1292 | line::Line::new(col, 1.0).draw( 1293 | [top[0], top[1], top[0] + xoffset, top[1]], 1294 | &c.draw_state, 1295 | c.transform, 1296 | g, 1297 | ); 1298 | line::Line::new(col, 1.0).draw( 1299 | [top[0] + xoffset, top[1], bot[0] + xoffset, bot[1]], 1300 | &c.draw_state, 1301 | c.transform, 1302 | g, 1303 | ); 1304 | line::Line::new(col, 1.0).draw( 1305 | [bot[0] + xoffset, bot[1], bot[0], bot[1]], 1306 | &c.draw_state, 1307 | c.transform, 1308 | g, 1309 | ); 1310 | 1311 | line::Line::new(col, 1.0).draw( 1312 | [val[0], val[1], gol[0], gol[1]], 1313 | &c.draw_state, 1314 | c.transform, 1315 | g, 1316 | ); 1317 | } 1318 | 1319 | draw_text( 1320 | key, 1321 | vec2(top[0] as f32 + 2.0, top[1] as f32 - 6.0), 1322 | col, 1323 | 10, 1324 | d, 1325 | c, 1326 | w, 1327 | ); 1328 | } 1329 | 1330 | /// Draw a little animated shape to show the modulator affecting its rotation 1331 | fn draw_anishape( 1332 | val: f32, 1333 | ord: usize, 1334 | col: usize, 1335 | d: &mut DrawData, 1336 | c: &Context, 1337 | w: &mut PistonWindow, 1338 | ) { 1339 | let dp = prims::DrawParms::new() 1340 | .color([d.theme[col], d.theme[col]]) 1341 | .thickness([2.0, 2.0]) 1342 | .connect_subs(4); 1343 | let trs = Dec2 { 1344 | scale: 80.0, 1345 | disp: vec2(d.dims[0] as f32 - 100.0, 100.0 + 160.0 * ord as f32), 1346 | rot: Rotation2::from_angle(Rad(val * 2.0)), 1347 | }; 1348 | 1349 | let (lines, parms) = prims::n_gon(3, 0.7, dp, &trs, None); 1350 | 1351 | let mut cd = d.prim_context(w); 1352 | cd.draw_lines(&lines, &parms, &c); 1353 | } 1354 | 1355 | /// Plot some text 1356 | fn draw_text( 1357 | tx: &str, 1358 | at: Vec2, 1359 | col: types::Color, 1360 | size: types::FontSize, 1361 | d: &mut DrawData, 1362 | c: &Context, 1363 | w: &mut PistonWindow, 1364 | ) { 1365 | let g = &mut d.draw_context(w); 1366 | let transform = c.transform.trans(at.x as f64, at.y as f64); 1367 | 1368 | text::Text::new_color(col, size) 1369 | .draw(&tx, &mut d.glyphs, &c.draw_state, transform, g) 1370 | .unwrap(); 1371 | } 1372 | 1373 | /// Draw a sample buffer as a fading, scrolling smooth connected line 1374 | fn draw_buf( 1375 | cur: f32, 1376 | buf: &VecDeque, 1377 | col: usize, 1378 | st: &StateData, 1379 | d: &mut DrawData, 1380 | c: &Context, 1381 | w: &mut PistonWindow, 1382 | ) { 1383 | let n = buf.len(); 1384 | if n == 0 { 1385 | return; 1386 | } 1387 | 1388 | let xbase = d.hdims[0] as f32; 1389 | let dom = (d.domain[1] - d.domain[0]) * 0.5; 1390 | let ydim = d.hdims[1] as f32; 1391 | 1392 | let mut ls = Vec::with_capacity(n); 1393 | let mut l0 = vec2(xbase, ydim - cur / dom * ydim); 1394 | 1395 | let sample_time = 1.0 / st.buf_samples_per_sec as f32; // time between taking samples 1396 | let sample_vel = st.buf_scroll_vel * sample_time; // displacement over one period 1397 | let frac_period = st.sampling_dt as f32 / 1_000_000.0; // fraction of current period 1398 | let xbase = xbase - frac_period * sample_vel; // displacement over fraction of period 1399 | 1400 | for i in 1..n { 1401 | let l1 = vec2(xbase - sample_vel * i as f32, ydim - buf[i] / dom * ydim); 1402 | ls.push([l0, l1]); 1403 | l0 = l1; 1404 | } 1405 | 1406 | let cl = d.theme[col]; 1407 | let rad = d.dot_rad as f32; 1408 | let dp = prims::DrawParms { 1409 | color: [cl, [cl[0], cl[1], cl[2], 0.0]], 1410 | thickness: [rad, 0.0], 1411 | cap: [prims::CapStyle::Round(8), prims::CapStyle::Round(8)], 1412 | connect_subs: 4, 1413 | }; 1414 | 1415 | d.prim_context(w) 1416 | .draw_lines_auto(&ls, dp, 0.0, false, false, None, c); 1417 | } 1418 | 1419 | /// ### 1420 | /// ### Render drivers 1421 | /// ### 1422 | 1423 | /// Clear background and draw the grid/axis 1424 | fn draw_ruler(d: &mut DrawData, c: &Context, w: &mut PistonWindow) { 1425 | line::Line::new(d.theme[8], 1.0).draw( 1426 | [d.hdims[0], d.dims[1], d.hdims[0], 0.0], 1427 | &c.draw_state, 1428 | c.transform, 1429 | &mut d.draw_context(w), 1430 | ); 1431 | 1432 | let domain = (d.domain[1] - d.domain[0]) as f64; 1433 | let guess = d.px_target / d.dims[1] * domain; 1434 | 1435 | let exp = guess.log(d.px_base); 1436 | let floored = exp.floor(); 1437 | let sdom = d.px_base.powf(floored); 1438 | 1439 | let x = floored + 1.0 - exp; 1440 | let scale = (3.0 - 2.0 * x) * x * x; // smooth step 1441 | 1442 | let center = d.domain[0] as f64 + domain * 0.5; 1443 | draw_tack(center, 1.0, d, c, w); 1444 | 1445 | let mut at = center + sdom; 1446 | let mut odd = true; 1447 | 1448 | while at < d.domain[1] as f64 { 1449 | draw_tack(at, if odd { scale } else { 1.0 }, d, c, w); 1450 | at += sdom; 1451 | odd = odd == false; 1452 | } 1453 | 1454 | let mut at = center - sdom; 1455 | let mut odd = true; 1456 | 1457 | while at > d.domain[0] as f64 { 1458 | draw_tack(at, if odd { scale } else { 1.0 }, d, c, w); 1459 | at -= sdom; 1460 | odd = odd == false; 1461 | } 1462 | } 1463 | 1464 | /// Generic modulator drawing 1465 | fn draw_modulator( 1466 | key: &str, 1467 | ordinal: &mut usize, 1468 | col: usize, 1469 | st: &mut StateData, 1470 | d: &mut DrawData, 1471 | c: &Context, 1472 | w: &mut PistonWindow, 1473 | ) { 1474 | let m = if let Some(s) = get_modulator(key, st) { 1475 | s 1476 | } else { 1477 | return; 1478 | }; 1479 | 1480 | if !m.enabled() { 1481 | return; 1482 | } 1483 | 1484 | let val = m.value(); 1485 | let gol = m.goal(); 1486 | 1487 | if let Some(b) = st.bufs.get(key) { 1488 | draw_buf(val, b, col, st, d, c, w); 1489 | } else { 1490 | draw_dot(val as f64, col, d, c, w); 1491 | } 1492 | 1493 | draw_label(val as f64, col, d, c, w); 1494 | draw_bracket(key, val, gol, m.range(), *ordinal, col, d, c, w); 1495 | draw_anishape(val, *ordinal, col, d, c, w); 1496 | 1497 | *ordinal += 1; 1498 | } 1499 | 1500 | /// ### 1501 | /// ### Color utilities 1502 | /// ### 1503 | 1504 | /// Create and return a color theme table 1505 | fn color_theme() -> [types::Color; 14] { 1506 | [ 1507 | prims::color_from(0xff_24_24_2e), // background 1508 | prims::color_from(0xff_be_be_ef), // darkSPACE: main colors (5 shades, bright to dim) 1509 | prims::color_from(0xff_86_86_cb), 1510 | prims::color_from(0xff_72_72_a1), 1511 | prims::color_from(0xff_5b_5b_7b), 1512 | prims::color_from(0xff_49_49_5a), 1513 | prims::color_from(0xff_fe_77_34), // darkSPACE: sub color (3 shades, bright to dim) 1514 | prims::color_from(0xff_b0_68_45), 1515 | prims::color_from(0xff_64_45_40), 1516 | prims::color_from(0xff_dd_f8_dd), // darkFOREST: more colors (5 shades, bright to dim) 1517 | prims::color_from(0xff_a9_bc_a9), 1518 | prims::color_from(0xff_86_98_86), 1519 | prims::color_from(0xff_73_82_73), 1520 | prims::color_from(0xff_58_5f_58), 1521 | ] 1522 | } 1523 | 1524 | fn blended_theme_color(realindex: f32, d: &DrawData) -> types::Color { 1525 | let i = realindex as usize; 1526 | let j = if i < d.theme.len() - 1 { i + 1 } else { 1 }; 1527 | let f = realindex - (i as f32); 1528 | 1529 | prims::col_lerp(f, prims::as_vec4(d.theme[i]), prims::as_vec4(d.theme[j])) 1530 | } 1531 | 1532 | /// ### 1533 | /// ### Other functions 1534 | /// ### 1535 | 1536 | /// Advance time and update the earlier time, returns elapsed microseconds 1537 | fn time_delta(earlier: &mut Instant) -> u64 { 1538 | let now = Instant::now(); 1539 | let dt = ModulatorEnv::::duration_to_micros(now.duration_since(*earlier)); 1540 | *earlier = now; 1541 | 1542 | dt 1543 | } 1544 | 1545 | /// Update the simulation 1546 | fn update(e: &piston_window::Event, dt: u64, st: &mut StateData, d: &mut DrawData, w: &PistonWindow) { 1547 | if st.paused == false { 1548 | st.m1.advance(dt); 1549 | st.m2.advance(dt); 1550 | 1551 | if st.curve_animate { 1552 | st.m3.advance(dt); 1553 | } 1554 | 1555 | // collect the samples of modulators that have buffers 1556 | st.sampling_dt += dt; 1557 | let wait_us = ((1.0 / st.buf_samples_per_sec as f32) * 1_000_000.0) as u64; 1558 | 1559 | if st.sampling_dt >= wait_us { 1560 | let sample_count = (st.buf_samples_per_sec as f32 * st.buf_max_fade_time) as usize; 1561 | 1562 | for (key, buf) in &mut st.bufs { 1563 | let md = if let Some(m) = st.m1.get(key) { 1564 | m 1565 | } else if let Some(m) = st.m2.get(key) { 1566 | m 1567 | } else { 1568 | continue; 1569 | }; 1570 | 1571 | if buf.len() >= sample_count { 1572 | let _ = buf.pop_back(); 1573 | } 1574 | buf.push_front(md.value()); 1575 | } 1576 | 1577 | st.sampling_dt = 0; 1578 | } 1579 | } 1580 | 1581 | // Use amp_mod to modulate the amp of waveform2 1582 | let ampmod = st.m2.value("amp_mod"); 1583 | if let Some(sw) = st.m2.get_mut("waveform2") { 1584 | if let Some(ss) = sw.as_any().downcast_mut::() { 1585 | ss.amplitude = 1.0 + ampmod; 1586 | } 1587 | } 1588 | 1589 | if let Some(sw) = st.m1.get_mut("waveform") { 1590 | if let Some(ss) = sw.as_any().downcast_mut::() { 1591 | ss.amplitude = 1.0 + ampmod * 0.25; 1592 | } 1593 | } 1594 | 1595 | if let Some(sw) = st.m1.get_mut("waveform") { 1596 | if let Some(ss) = sw.as_any().downcast_mut::() { 1597 | ss.amplitude = 1.0 + ampmod * 0.25; 1598 | } 1599 | } 1600 | 1601 | let mods = st.m1.get_mods(); 1602 | d.domain = [-1.0, 1.0]; 1603 | 1604 | for (_, v) in mods { 1605 | if v.enabled() { 1606 | let q = v.range(); 1607 | 1608 | if q[0] < d.domain[0] { 1609 | d.domain[0] = q[0]; 1610 | } 1611 | if q[1] > d.domain[1] { 1612 | d.domain[1] = q[1]; 1613 | } 1614 | } 1615 | } 1616 | 1617 | // We make the domain centered around 0 for now 1618 | let abs_max = f32::max(d.domain[0].abs(), d.domain[1].abs()); 1619 | 1620 | d.domain[0] = abs_max * -d.margin_scale; 1621 | d.domain[1] = abs_max * d.margin_scale; 1622 | 1623 | // Cache the current mouse position 1624 | if let Some(args) = e.mouse_cursor_args() { 1625 | let dpi = w.window.window.get_hidpi_factor() as f32; 1626 | d.mouse_pos = vec2(args[0] as f32 * dpi, args[1] as f32 * dpi); 1627 | } 1628 | 1629 | // Track the mouse pressed/released state 1630 | if let Some(args) = e.button_args() { 1631 | // If we wanted to track key presses, they would be received here as well 1632 | if args.state == ButtonState::Press { 1633 | st.tracking = true; 1634 | st.tracking_from = d.mouse_pos; 1635 | } 1636 | if args.state == ButtonState::Release { 1637 | st.tracking = false; 1638 | } 1639 | } 1640 | 1641 | // Track scrolling with the mouse wheel 1642 | if let Some(args) = e.mouse_scroll_args() { 1643 | st.mouse_scroll = vec2(args[0] as f32, args[1] as f32) 1644 | } 1645 | // Track entered text 1646 | if let Some(args) = e.text_args() { 1647 | st.entered_text = args 1648 | } 1649 | } 1650 | -------------------------------------------------------------------------------- /src/prims.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Extended primitives 3 | //! 4 | //! Customized version of gfx_graphics::GfxGraphics that gives us public access to the values 5 | //! in the rendering context and adds custom rendering for prims, including thickness and color 6 | //! blending, smooth connecting geometry, curve rendering and more 7 | 8 | use cgmath::prelude::*; 9 | use cgmath::{vec2, vec4, Basis2, Decomposed, Matrix2, Vector2, Vector4}; 10 | 11 | type Vec2 = Vector2; 12 | type Vec4 = Vector4; 13 | type Mat2 = Matrix2; 14 | type Bas2 = Basis2; 15 | type Dec2 = Decomposed; 16 | 17 | use num_complex::Complex32; 18 | 19 | use std::f32; 20 | use std::f32::consts::*; 21 | use std::slice::from_raw_parts; 22 | 23 | use bezier::{vec2_in_bounds, vec2_norm_or_zero, CubicCurve}; 24 | 25 | // 26 | // Geometry generators and utilities 27 | // 28 | 29 | /// Generate an n-gon of `sides` sides, side length scaled down by `slide_scale`, based off the 30 | /// draw parameters `with` and transformed by the decomposed translate/rotate/scale `trs` - the 31 | /// vertices of the polygon are generated by rotating the unit vector `from` (unit y if `None`) 32 | /// around the unit circle at the origin and transforming the result by trs. 33 | pub fn n_gon( 34 | sides: usize, 35 | side_scale: f32, 36 | with: DrawParms, 37 | trs: &Dec2, 38 | from: Option, 39 | ) -> (Vec<[Vec2; 2]>, Vec) { 40 | let mut lines = vec![[vec2(0.0, 0.0); 2]; sides]; 41 | let parms = vec![with; sides]; 42 | 43 | let angle = PI * 2.0 / sides as f32; 44 | let ss = side_scale.max(0.001).min(1.0); 45 | 46 | let nn = if let Some(f) = from { 47 | f 48 | } else { 49 | vec2(0.0, -1.0) 50 | }; // starting vector 51 | let mut v0 = trs.disp + trs.transform_vector(nn); 52 | 53 | for i in 0..sides { 54 | let an = angle * (i + 1) as f32; 55 | let (sn, cs) = an.sin_cos(); 56 | 57 | let v1 = vec2(nn.x * cs + nn.y * sn, nn.y * cs - nn.x * sn); 58 | let v1 = trs.disp + trs.transform_vector(v1); 59 | 60 | if ss < 1.0 { 61 | let cn = v0 + (v1 - v0) * 0.5; 62 | lines[i] = [cn + (v0 - cn) * ss, cn + (v1 - cn) * ss]; 63 | } else { 64 | lines[i] = [v0, v1]; 65 | } 66 | v0 = v1; 67 | } 68 | 69 | (lines, parms) 70 | } 71 | 72 | // 73 | // Draw parameters for custom primitives 74 | // 75 | 76 | /// Drawing styles available for each end cap 77 | #[derive(Copy, Clone, Debug)] 78 | pub enum CapStyle { 79 | Round(u16), // rounded end cap with given subdivisions 80 | Square(f32), // squared end cap, to given distance (0.0 == use thickness) 81 | None, // no cap 82 | } 83 | 84 | /// Line segment rendering parameters 85 | #[derive(Copy, Clone, Debug)] 86 | pub struct DrawParms { 87 | pub color: [Color; 2], // colors at time 0.0 and 1.0 88 | pub thickness: [f32; 2], // thickness (on each side) at time 0.0 and 1.0 89 | pub cap: [CapStyle; 2], // end cap style at 0.0 and 1.0 90 | pub connect_subs: u16, // subdivisions to connect to next line in a set (0 to disable) 91 | } 92 | 93 | impl DrawParms { 94 | /// Build a draw parameter record 95 | pub fn new() -> Self { 96 | DrawParms { 97 | color: [[1.0; 4], [1.0; 4]], 98 | thickness: [1.0, 1.0], 99 | cap: [CapStyle::None, CapStyle::None], 100 | connect_subs: 16, 101 | } 102 | } 103 | 104 | /// Builder: set the color 105 | pub fn color(mut self, c: [Color; 2]) -> Self { 106 | self.color = c; 107 | self 108 | } 109 | /// Builder: set the thickness 110 | pub fn thickness(mut self, t: [f32; 2]) -> Self { 111 | self.thickness = t; 112 | self 113 | } 114 | /// Builder: set the endpoint style 115 | pub fn cap(mut self, c: [CapStyle; 2]) -> Self { 116 | self.cap = c; 117 | self 118 | } 119 | /// Builder: set number of subdivisions for the connector (0 = no connector) 120 | pub fn connect_subs(mut self, n: u16) -> Self { 121 | self.connect_subs = n; 122 | self 123 | } 124 | } 125 | 126 | // 127 | // Extra parameters for curve rendering 128 | // 129 | 130 | /// Record returned by the `PrimGraphics` `draw` method reflecting the result of querying 131 | /// for curve features, always `None` if given `tolerance` of 0 132 | #[derive(Copy, Clone, Debug)] 133 | pub enum CurveQueryResult { 134 | /// Found a control point within tolerance (highest priority) 135 | Control { 136 | k: usize, // index of the control point [0..4] 137 | pt: Vec2, // control point location 138 | distance: f32, // distance to query point 139 | }, 140 | /// Found a control handle point within tolerance 141 | Handle { 142 | k: usize, // index of the handle [0..1] 143 | pt: Vec2, // point on handle location 144 | time: f32, // time along handle of point location 145 | distance: f32, // distance to query point 146 | }, 147 | /// Found a point on a linear sample approximation segment within tolerance 148 | Sample { 149 | k: usize, // index of sample containing the closest point on its linear approximation 150 | pt: Vec2, // point on linear approximation of curve 151 | time: f32, // time along the approximation sample line 152 | distance: f32, // distance to query point 153 | }, 154 | None, 155 | } 156 | 157 | impl CurveQueryResult { 158 | fn new() -> Self { 159 | CurveQueryResult::None 160 | } 161 | } 162 | 163 | /// Settings used to draw a curve 164 | #[derive(Copy, Clone, Debug)] 165 | pub struct CurveDrawParms { 166 | pub parms: DrawParms, // overall color, thickness, start/end caps to draw the curve 167 | pub segment_scale: f32, // used to scale the endpoints of each line segment generated 168 | 169 | pub extra_features: bool, // if not set all extra render options below are disabled 170 | pub features_if: bool, // if set, enabled features other than curve drawn only if pt in bounds 171 | pub samples: bool, // draw the sample points 172 | pub normals: bool, // draw the normals at the sample points 173 | pub tangents: bool, // draw the tangents at the sample points 174 | pub bounds: bool, // draw the sample bounding box 175 | pub controls: bool, // draw the control points/handles 176 | pub feature_info: bool, // print extended feature info, if found 177 | 178 | pub tolerance: f32, // distance range for searches 179 | 180 | pub sample_radius: f32, // radius for sample points 181 | pub control_radius: f32, // radius for control points 182 | 183 | pub circle_subs: u16, // subdivisions for circles used to draw samples and handles 184 | 185 | pub vlen: f32, // length for visualization of normals/tangents 186 | 187 | pub lthick: f32, // thickness for normal and tangent lines 188 | pub cthick: f32, // thickness for control handles 189 | 190 | pub samplc: Color, // color for sample points, normals, control points/handles 191 | pub normlc: Color, // color for tangents, bounds 192 | pub selecc: Color, // color for selected items 193 | } 194 | 195 | impl CurveDrawParms { 196 | /// Make a new curve rendering settings record 197 | pub fn new() -> Self { 198 | Self { 199 | parms: DrawParms::new() 200 | .cap([CapStyle::Round(8); 2]) 201 | .connect_subs(0), 202 | segment_scale: 1.0, 203 | 204 | extra_features: false, 205 | features_if: true, 206 | samples: true, 207 | normals: false, 208 | tangents: false, 209 | bounds: true, 210 | controls: true, 211 | feature_info: true, 212 | 213 | tolerance: 8.0, 214 | 215 | sample_radius: 3.0, 216 | control_radius: 5.0, 217 | 218 | circle_subs: 10, 219 | 220 | vlen: 30.0, 221 | 222 | lthick: 1.5, 223 | cthick: 2.0, 224 | 225 | samplc: color_from(0x8f_34_fe_bb), 226 | normlc: color_from(0xaf_42_67_5a), 227 | selecc: color_from(0xff_ff_ff_00), 228 | } 229 | } 230 | 231 | /// Builder: overall color, thickness, caps to draw the curve 232 | pub fn parms(mut self, p: DrawParms) -> Self { 233 | self.parms = p; 234 | self 235 | } 236 | /// Builder: scaling factor for rendering line segments 237 | pub fn segment_scale(mut self, n: f32) -> Self { 238 | self.segment_scale = n; 239 | self 240 | } 241 | 242 | /// Builder: if not set all other render features are disabled, regardless of settings 243 | pub fn extra_features(mut self, s: bool) -> Self { 244 | self.extra_features = s; 245 | self 246 | } 247 | /// Builder: if set, enabled features other than curve drawn only if pt in bounds 248 | pub fn features_if(mut self, s: bool) -> Self { 249 | self.features_if = s; 250 | self 251 | } 252 | /// Builder: draw the sample points 253 | pub fn samples(mut self, s: bool) -> Self { 254 | self.samples = s; 255 | self 256 | } 257 | /// Builder: draw the normals at the sample points 258 | pub fn normals(mut self, s: bool) -> Self { 259 | self.normals = s; 260 | self 261 | } 262 | /// Builder: draw the tangents at the sample points 263 | pub fn tangents(mut self, s: bool) -> Self { 264 | self.tangents = s; 265 | self 266 | } 267 | /// Builder: draw the sample bounding box 268 | pub fn bounds(mut self, s: bool) -> Self { 269 | self.bounds = s; 270 | self 271 | } 272 | /// Builder: draw the control points/handles 273 | pub fn controls(mut self, s: bool) -> Self { 274 | self.controls = s; 275 | self 276 | } 277 | /// Builder: print extended feature info, if found 278 | pub fn feature_info(mut self, s: bool) -> Self { 279 | self.feature_info = s; 280 | self 281 | } 282 | 283 | /// Builder: distance range for searches 284 | pub fn tolerance(mut self, n: f32) -> Self { 285 | self.tolerance = n; 286 | self 287 | } 288 | 289 | /// Builder: radius for sample points 290 | pub fn sample_radius(mut self, n: f32) -> Self { 291 | self.sample_radius = n; 292 | self 293 | } 294 | /// Builder: radius for control points 295 | pub fn control_radius(mut self, n: f32) -> Self { 296 | self.control_radius = n; 297 | self 298 | } 299 | 300 | /// Builder: subdivisions for sample/control circles 301 | pub fn circle_subs(mut self, n: u16) -> Self { 302 | self.circle_subs = n; 303 | self 304 | } 305 | 306 | /// Builder: length for visualization of normals/tangents 307 | pub fn vlen(mut self, n: f32) -> Self { 308 | self.vlen = n; 309 | self 310 | } 311 | 312 | /// Builder: thickness for normal and tangent lines 313 | pub fn lthick(mut self, n: f32) -> Self { 314 | self.lthick = n; 315 | self 316 | } 317 | /// Builder: thickness for control handles 318 | pub fn cthick(mut self, n: f32) -> Self { 319 | self.cthick = n; 320 | self 321 | } 322 | 323 | /// Builder: color for sample points, control points/handles 324 | pub fn samplc(mut self, c: Color) -> Self { 325 | self.samplc = c; 326 | self 327 | } 328 | /// Builder: color for normals, tangents 329 | pub fn normlc(mut self, c: Color) -> Self { 330 | self.normlc = c; 331 | self 332 | } 333 | /// Builder: color for selected/highlighted item 334 | pub fn selecc(mut self, c: Color) -> Self { 335 | self.selecc = c; 336 | self 337 | } 338 | 339 | /// Find the closest control, point on control handle or point on curve, in order of priority 340 | pub fn find(&self, curve: &mut CubicCurve, p: Vec2) -> CurveQueryResult { 341 | assert!(self.tolerance < f32::INFINITY); 342 | curve.validate(); 343 | 344 | if self.tolerance <= 0.0 { 345 | return CurveQueryResult::new(); 346 | } 347 | 348 | let (k, distance) = self.control(curve, p); 349 | if distance <= self.tolerance { 350 | return CurveQueryResult::Control { 351 | k, 352 | pt: curve.control_points()[k], 353 | distance, 354 | }; 355 | } 356 | 357 | let (k, pt, time, distance) = self.handle(curve, p); 358 | if distance <= self.tolerance { 359 | return CurveQueryResult::Handle { 360 | k, 361 | pt, 362 | time, 363 | distance, 364 | }; 365 | } 366 | 367 | if vec2_in_bounds(p, &curve.bounds(self.tolerance)) { 368 | let (k, pt, time, distance) = self.curve(curve, p); 369 | if let Some(j) = k { 370 | if distance <= self.tolerance { 371 | return CurveQueryResult::Sample { 372 | k: j, 373 | pt, 374 | time, 375 | distance, 376 | }; 377 | } 378 | } 379 | } 380 | 381 | return CurveQueryResult::new(); 382 | } 383 | 384 | // Query component search 385 | fn control(&self, curve: &mut CubicCurve, p: Vec2) -> (usize, f32) { 386 | if self.tolerance > 0.0 && self.controls { 387 | curve.closest_control_point_to(p) 388 | } else { 389 | (0, f32::INFINITY) 390 | } 391 | } 392 | fn handle(&self, curve: &mut CubicCurve, p: Vec2) -> (usize, Vec2, f32, f32) { 393 | if self.tolerance > 0.0 && self.controls { 394 | curve.closest_handle_to(p) 395 | } else { 396 | (0, vec2(0.0, 0.0), 0.0, f32::INFINITY) 397 | } 398 | } 399 | fn curve(&self, curve: &mut CubicCurve, p: Vec2) -> (Option, Vec2, f32, f32) { 400 | if self.tolerance > 0.0 { 401 | curve.closest_to(p) 402 | } else { 403 | (None, vec2(0.0, 0.0), 0.0, f32::INFINITY) 404 | } 405 | } 406 | } 407 | 408 | // 409 | // Customized piston graphics environment to support our extended primitives 410 | // 411 | 412 | pub struct PrimGraphics<'a, R, C> 413 | where 414 | R: gfx::Resources + 'a, 415 | C: gfx::CommandBuffer + 'a, 416 | R::Buffer: 'a, 417 | R::Shader: 'a, 418 | R::Program: 'a, 419 | R::Texture: 'a, 420 | R::Sampler: 'a, 421 | { 422 | pub encoder: &'a mut gfx::Encoder, // data taken straight from piston graphics 423 | pub output_color: &'a gfx::handle::RenderTargetView, 424 | pub output_stencil: &'a gfx::handle::DepthStencilView, 425 | 426 | pub c2d: &'a mut Prims2d, // our custom 2d buffers 427 | 428 | pub debug_wireframe: bool, // draw extra debug lines 429 | pub debug_wireframe_color: Color, // color to use for debug lines 430 | pub debug_faces: bool, // draw all faces in alternating translucent colors 431 | 432 | flip_debug_color: bool, // internal state to choose alternating quad colors 433 | fill_center: Option, // internal state, if Some(center) then fill mode enabled 434 | fill_color: Color, // internal state, color to use for center point when fillings 435 | 436 | lerp_exp: Option, // internal state - linear mapping to use with connectors 437 | } 438 | 439 | impl<'a, R, C> PrimGraphics<'a, R, C> 440 | where 441 | R: gfx::Resources, 442 | C: gfx::CommandBuffer, 443 | { 444 | pub fn new( 445 | encoder: &'a mut gfx::Encoder, 446 | output_color: &'a gfx::handle::RenderTargetView, 447 | output_stencil: &'a gfx::handle::DepthStencilView, 448 | c2d: &'a mut Prims2d, 449 | ) -> Self { 450 | PrimGraphics { 451 | encoder: encoder, 452 | output_color: output_color, 453 | output_stencil: output_stencil, 454 | c2d: c2d, 455 | 456 | debug_wireframe: false, 457 | debug_wireframe_color: [0.0, 0.0, 0.0, 1.0], 458 | debug_faces: false, 459 | 460 | flip_debug_color: false, 461 | fill_center: None, 462 | fill_color: [0.0; 4], 463 | 464 | lerp_exp: None, 465 | } 466 | } 467 | 468 | /// Draw a line 469 | pub fn draw_line(&mut self, ps: [Vec2; 2], parms: &DrawParms, c: &Context) { 470 | let (ln, nn) = line_parms(ps); 471 | 472 | let p0 = match parms.cap[0] { 473 | CapStyle::Square(n) => ps[0] - ln * if n > 0.0 { n } else { parms.thickness[0] }, 474 | _ => ps[0], 475 | }; 476 | let p1 = match parms.cap[1] { 477 | CapStyle::Square(n) => ps[1] + ln * if n > 0.0 { n } else { parms.thickness[1] }, 478 | _ => ps[1], 479 | }; 480 | 481 | let n0 = nn * parms.thickness[0]; 482 | let n1 = nn * parms.thickness[1]; 483 | 484 | self.add_quad(&[p0 - n0, p0 + n0, p1 - n1, p1 + n1], parms.color, c); 485 | 486 | match parms.cap[0] { 487 | CapStyle::Round(sub) => self.round_t(ps[0], nn, sub, 0, parms, c), 488 | _ => (), 489 | }; 490 | match parms.cap[1] { 491 | CapStyle::Round(sub) => self.round_t(ps[1], nn, sub, 1, parms, c), 492 | _ => (), 493 | }; 494 | } 495 | 496 | /// Draw the a set of lines generating intermediate parameters from `parm` automatically, 497 | /// distributing the given parameter space over the estimated linear length of the set. 498 | /// 499 | /// If `closed` is true the endpoints are connected. The `scale` parameter can be used to 500 | /// scale the internal line segments, in conjunction with `parm.connect_subs` this adjusts 501 | /// the overall curvature of the connections. 502 | /// 503 | /// The `scale` parameter is interpreted as follows: 0.0 = automatic absolute (uses 504 | /// current thickness), (0.0, 1.0] = proportional scale factor around segment centerpoint, 505 | /// and >1.0 = absolute scaling by the given amount, at each end. 506 | /// 507 | /// The `lerp_exp` value is used to map the linear blend factor generated for color and 508 | /// thickness blending from linear to exponential - linear exp 1.0 is used if `None`, 509 | /// otherwise the value is used as the exponent to raise the blending factor to. 510 | pub fn draw_lines_auto( 511 | &mut self, 512 | lines: &[[Vec2; 2]], 513 | parm: DrawParms, 514 | scale: f32, 515 | closed: bool, 516 | filled: bool, 517 | lerp_exp: Option, 518 | c: &Context, 519 | ) { 520 | let n = lines.len(); 521 | if n == 0 { 522 | return; 523 | } 524 | 525 | let (ls, ln) = corrected_lines(&lines, scale, &parm, closed, lerp_exp); 526 | let ps = auto_parms(&ls, ln, &parm, closed, lerp_exp); 527 | 528 | self.lerp_exp = lerp_exp; 529 | if filled { 530 | self.draw_lines_filled(&ls, &ps, c); 531 | } else { 532 | self.draw_lines(&ls, &ps, c); 533 | } 534 | self.lerp_exp = None; 535 | } 536 | 537 | /// Draw a set of lines 538 | pub fn draw_lines(&mut self, lines: &[[Vec2; 2]], parms: &[DrawParms], c: &Context) { 539 | let n = lines.len(); 540 | assert_eq!(n, parms.len()); 541 | 542 | for i in 0..n { 543 | self.draw_line(lines[i], &parms[i], c); 544 | 545 | if parms[i].connect_subs > 0 { 546 | let j = if i < n - 1 { i + 1 } else { 0 }; 547 | 548 | let (_, nn0) = line_parms(lines[i]); 549 | let (_, nn1) = line_parms(lines[j]); 550 | 551 | let n0 = nn0 * parms[i].thickness[1]; 552 | let f = [lines[i][1] + n0, lines[i][1] - n0]; 553 | 554 | let n1 = nn1 * parms[j].thickness[0]; 555 | let t = [lines[j][0] + n1, lines[j][0] - n1]; 556 | 557 | let cols = [parms[i].color[1], parms[j].color[0]]; 558 | self.connect(f, t, parms[i].connect_subs, cols, c); 559 | } 560 | } 561 | } 562 | 563 | /// Draw the set of lines, also fill out to the center (works best if closed, of course) 564 | pub fn draw_lines_filled(&mut self, lines: &[[Vec2; 2]], parms: &[DrawParms], c: &Context) { 565 | let (center, color) = fill_parms(lines, parms); 566 | 567 | self.fill_center = Some(tv(center, c.transform)); 568 | self.fill_color = color; // already in linear space 569 | 570 | self.draw_lines(lines, parms, c); 571 | 572 | self.fill_center = None; 573 | } 574 | 575 | /// Draw a curve. Notice that `curve` is mutably borrowed because its validation might cause 576 | /// the sample data to be recomputed. The `lerp_exp` parameter has the same meaning as with 577 | /// the `draw_lines_auto` method: it Some(e) then e is the exponent used to map the linear 578 | /// blend factor into an exponential curve 579 | pub fn draw_curve( 580 | &mut self, 581 | curve: &mut CubicCurve, 582 | parms: &CurveDrawParms, 583 | query_loc: Vec2, 584 | query_result: &CurveQueryResult, 585 | lerp_exp: Option, 586 | c: &Context, 587 | ) { 588 | curve.validate(); // must make sure the curve is valid or its methods will panic 589 | 590 | let smp = curve.samples(); 591 | let n = smp.len(); 592 | 593 | if n < 2 { 594 | return; 595 | } 596 | 597 | let mut ls = Vec::with_capacity(n - 1); 598 | for i in 0..n - 1 { 599 | ls.push([smp[i], smp[i + 1]]); 600 | } 601 | 602 | self.draw_lines_auto( 603 | &ls, 604 | parms.parms, 605 | parms.segment_scale, 606 | false, 607 | false, 608 | lerp_exp, 609 | c, 610 | ); 611 | 612 | if !parms.extra_features { 613 | return; 614 | } 615 | 616 | if !parms.features_if || curve.in_bounds(query_loc, parms.sample_radius) { 617 | if parms.bounds { 618 | self.draw_rect( 619 | curve.bounds(parms.sample_radius), 620 | parms.samplc, 621 | parms.lthick, 622 | false, 623 | c, 624 | ); 625 | } 626 | 627 | if parms.samples { 628 | for p in smp { 629 | self.draw_circle(*p, parms.sample_radius, parms.samplc, parms.circle_subs, c); 630 | } 631 | } 632 | 633 | if let CurveQueryResult::Sample { 634 | k: _, 635 | pt, 636 | time: _, 637 | distance: _, 638 | } = query_result 639 | { 640 | self.draw_circle( 641 | *pt, 642 | parms.sample_radius * 2.0, 643 | parms.selecc, 644 | parms.circle_subs, 645 | c, 646 | ); 647 | } 648 | 649 | if parms.normals || parms.tangents { 650 | let smd = curve.sampledata(); 651 | let pm = DrawParms { 652 | color: [parms.samplc, parms.normlc], 653 | thickness: [parms.lthick * 0.5, parms.lthick * 0.5], 654 | ..DrawParms::new() 655 | }; 656 | 657 | for i in 0..n { 658 | if parms.normals { 659 | self.draw_line( 660 | [smp[i], smp[i] + curve.tangent_at_(smd[i].n) * parms.vlen], 661 | &pm, 662 | &c, 663 | ); 664 | } 665 | if parms.tangents { 666 | self.draw_line([smp[i], smp[i] + smd[i].n * parms.vlen], &pm, &c); 667 | } 668 | } 669 | } 670 | } 671 | 672 | if parms.controls { 673 | // Get the current selection from the query 674 | let sel_h = if let CurveQueryResult::Handle { 675 | k, 676 | pt: _, 677 | time: _, 678 | distance: _, 679 | } = query_result 680 | { 681 | *k 682 | } else { 683 | 2 // never 684 | }; 685 | let sel_c = if let CurveQueryResult::Control { 686 | k, 687 | pt: _, 688 | distance: _, 689 | } = query_result 690 | { 691 | *k 692 | } else { 693 | 5 // never 694 | }; 695 | 696 | let cs = curve.control_points(); 697 | let pm = DrawParms { 698 | color: [parms.samplc, parms.samplc], 699 | thickness: [parms.cthick * 0.5, parms.cthick * 0.5], 700 | ..DrawParms::new() 701 | }; 702 | let hc = [parms.selecc, parms.selecc]; 703 | 704 | self.draw_line( 705 | [cs[0], cs[1]], 706 | &DrawParms { 707 | color: if sel_h != 0 { pm.color } else { hc }, 708 | ..pm 709 | }, 710 | &c, 711 | ); 712 | self.draw_line( 713 | [cs[3], cs[2]], 714 | &DrawParms { 715 | color: if sel_h != 1 { pm.color } else { hc }, 716 | ..pm 717 | }, 718 | &c, 719 | ); 720 | 721 | for i in 0..4 { 722 | self.draw_circle( 723 | cs[i], 724 | if sel_c != i { 725 | parms.control_radius 726 | } else { 727 | parms.control_radius * 1.5 728 | }, 729 | if sel_c != i { 730 | parms.samplc 731 | } else { 732 | parms.selecc 733 | }, 734 | parms.circle_subs, 735 | c, 736 | ); 737 | } 738 | } 739 | } 740 | 741 | /// Draw a circle 742 | pub fn draw_circle(&mut self, center: Vec2, radius: f32, color: Color, subs: u16, c: &Context) { 743 | let pm = DrawParms { 744 | color: [color, color], 745 | thickness: [radius, radius], 746 | cap: [CapStyle::None, CapStyle::None], 747 | connect_subs: 0, 748 | }; 749 | 750 | let norm = vec2(0.0, 1.0); 751 | let subs = u16::max(2, subs / 2); 752 | 753 | self.round_t(center, norm, subs, 0, &pm, c); 754 | self.round_t(center, norm, subs, 1, &pm, c); 755 | } 756 | 757 | /// Draw a rectangle 758 | pub fn draw_rect( 759 | &mut self, 760 | bounds: [Vec2; 2], 761 | color: Color, 762 | thickness: f32, 763 | filled: bool, 764 | c: &Context, 765 | ) { 766 | let ht = thickness / 2.0; 767 | let pm = DrawParms { 768 | color: [color, color], 769 | thickness: [ht, ht], 770 | cap: [CapStyle::Square(0.0), CapStyle::None], 771 | connect_subs: 0, 772 | }; 773 | 774 | let tl = bounds[0]; 775 | let br = bounds[1]; 776 | let tr = vec2(br.x, tl.y); 777 | let bl = vec2(tl.x, br.y); 778 | 779 | let ln = [ 780 | [tl, vec2(br.x - ht, tl.y)], 781 | [tr, vec2(br.x, br.y - ht)], 782 | [br, vec2(tl.x + ht, br.y)], 783 | [bl, vec2(tl.x, tl.y + ht)], 784 | ]; 785 | if filled { 786 | self.draw_lines_filled(&ln, &[pm; 4], &c); 787 | } else { 788 | self.draw_lines(&ln, &[pm; 4], &c); 789 | } 790 | } 791 | 792 | /// Get the quad drawing debug color so they alternate 793 | fn next_quad_colors(&mut self) -> [Color; 2] { 794 | debug_assert!(self.debug_faces); 795 | 796 | self.flip_debug_color = !self.flip_debug_color; 797 | if self.flip_debug_color { 798 | [[0.0, 0.7, 0.0, 0.7], [0.0, 0.7, 0.0, 0.7]] 799 | } else { 800 | [[0.7, 0.0, 0.0, 0.7], [0.7, 0.0, 0.0, 0.7]] 801 | } 802 | } 803 | 804 | /// Add a quad to render buffer 805 | fn add_quad(&mut self, quad: &[Vec2; 4], mut cl: [Color; 2], c: &Context) { 806 | let add_verts = if let Some(_) = self.fill_center { 9 } else { 6 }; 807 | self.validate_state(&c.draw_state, add_verts); 808 | 809 | if self.debug_faces { 810 | cl = self.next_quad_colors(); 811 | } 812 | 813 | let c0 = gamma_srgb_to_linear(cl[0]); 814 | let c1 = gamma_srgb_to_linear(cl[1]); 815 | 816 | let q0 = tv(quad[0], c.transform); 817 | let q1 = tv(quad[1], c.transform); 818 | let q2 = tv(quad[2], c.transform); 819 | let q3 = tv(quad[3], c.transform); 820 | 821 | let verts = [q0, q1, q2, q3, q2, q1]; 822 | let vcols = [c0, c0, c1, c1, c1, c0]; 823 | 824 | unsafe { 825 | // draw the main quad 826 | self.encoder 827 | .update_buffer( 828 | &self.c2d.pos, 829 | from_raw_parts(verts.as_ptr() as *const PositionFormat, 6), 830 | self.c2d.offset, 831 | ).unwrap(); 832 | self.encoder 833 | .update_buffer( 834 | &self.c2d.color, 835 | from_raw_parts(vcols.as_ptr() as *const ColorFormat, 6), 836 | self.c2d.offset, 837 | ).unwrap(); 838 | self.c2d.offset += 6; 839 | } 840 | 841 | if let Some(center) = self.fill_center { 842 | // if fill is on, draw a tri to cached center 843 | let (v0, v1) = towards_center(center, [q0, q1, q2, q3]); 844 | unsafe { 845 | self.encoder 846 | .update_buffer( 847 | &self.c2d.pos, 848 | from_raw_parts([center, v0, v1].as_ptr() as *const PositionFormat, 3), 849 | self.c2d.offset, 850 | ).unwrap(); 851 | self.encoder 852 | .update_buffer( 853 | &self.c2d.color, 854 | from_raw_parts([self.fill_color, c0, c1].as_ptr() as *const ColorFormat, 3), 855 | self.c2d.offset, 856 | ).unwrap(); 857 | self.c2d.offset += 3; 858 | } 859 | } 860 | 861 | if self.debug_wireframe { 862 | self.line_debug([quad[0], quad[1]], c); 863 | self.line_debug([quad[1], quad[3]], c); 864 | self.line_debug([quad[3], quad[2]], c); 865 | self.line_debug([quad[2], quad[0]], c); 866 | } 867 | } 868 | 869 | /// Generate quads connecting a segment to another - uses the Hise Vector Screw 870 | fn connect_t( 871 | &mut self, 872 | f: [Vec2; 2], // from segment 873 | t: [Vec2; 2], // to segment 874 | about: Vec2, // initial pivot point 875 | subs: u16, // number of quads to generate 876 | col: [Color; 2], // colors at f and t 877 | c: &Context, 878 | ) { 879 | let cn0 = corrected_pivot([f[0], t[0]], about); 880 | let cn1 = corrected_pivot([f[1], t[1]], about); 881 | 882 | let f0c = complex(f[0]) - cn0; 883 | let t0c = complex(t[0]) - cn0; 884 | let f1c = complex(f[1]) - cn1; 885 | let t1c = complex(t[1]) - cn1; 886 | 887 | let d0 = f0c.inv() * t0c; 888 | let d1 = f1c.inv() * t1c; 889 | 890 | let cf = as_vec4(col[0]); // colors for interpolation 891 | let ct = as_vec4(col[1]); 892 | 893 | let mut q0 = complex(f[0]); 894 | let mut q1 = complex(f[1]); 895 | 896 | let mut c0 = as_color(cf); 897 | 898 | for i in 0..subs { 899 | let mut ex = (i + 1) as f32 / subs as f32; // exponent to interpolate over 900 | if let Some(lex) = self.lerp_exp { 901 | ex = ex.powf(lex); 902 | } 903 | let c1 = col_lerp(ex, cf, ct); 904 | 905 | let p0 = if d0.im >= 0.0 { 906 | d0.powf(ex) 907 | } else { 908 | d0.conj().powf(ex).conj() // map to positive and back 909 | }; 910 | let p1 = if d1.im >= 0.0 { 911 | d1.powf(ex) 912 | } else { 913 | d1.conj().powf(ex).conj() 914 | }; 915 | 916 | let p0 = f0c * p0 + cn0; 917 | let p1 = f1c * p1 + cn1; 918 | 919 | self.add_quad( 920 | &[ 921 | vec2(q0.re, q0.im), 922 | vec2(q1.re, q1.im), 923 | vec2(p0.re, p0.im), 924 | vec2(p1.re, p1.im), 925 | ], 926 | [c0, c1], 927 | c, 928 | ); 929 | 930 | q0 = p0; 931 | q1 = p1; 932 | c0 = c1; 933 | } 934 | } 935 | 936 | /// Connect segment f to segment t with an arc 937 | fn connect(&mut self, f: [Vec2; 2], t: [Vec2; 2], subs: u16, col: [Color; 2], c: &Context) { 938 | assert!(subs > 0); 939 | 940 | let df = f[1] - f[0]; 941 | let dt = t[1] - t[0]; 942 | 943 | // Find the intersection of the line segments as the initial pivot point 944 | let mat = Mat2::new(df.x, df.y, dt.x, dt.y); 945 | let det = mat.determinant(); 946 | 947 | if det.abs() < 0.001 { 948 | self.add_quad(&[f[0], f[1], t[0], t[1]], col, c); 949 | } else if let Some(mat) = mat.invert() { 950 | self.connect_t( 951 | f, 952 | t, 953 | f[0] + (f[1] - f[0]) * (mat * (t[0] - f[0])).x, 954 | subs, 955 | col, 956 | c, 957 | ); 958 | } 959 | } 960 | 961 | /// Draw a rounded cap at `at` 962 | fn round_t( 963 | &mut self, 964 | at: Vec2, 965 | normal: Vec2, // normal at endpoint 966 | subs: u16, // desired subdivisions 967 | index: usize, // 0/1 index to get extra endpoint data 968 | parms: &DrawParms, 969 | c: &Context, 970 | ) { 971 | if subs == 0 { 972 | return; 973 | } 974 | 975 | let p0 = tv(at, c.transform); // base 976 | let p1 = tv(at + normal * parms.thickness[index], c.transform); // moving points 977 | 978 | let mut p = [p0, p1, vec2(0.0, 0.0)]; 979 | let mut cols = [gamma_srgb_to_linear(parms.color[index]); 3]; 980 | 981 | let tris = subs as usize; // number of generated triangles 982 | let angle = if index == 0 { 983 | PI / tris as f32 984 | } else { 985 | -PI / tris as f32 986 | }; 987 | 988 | self.validate_state(&c.draw_state, 3 * tris); 989 | 990 | for i in 0..tris { 991 | let an = angle * (i + 1) as f32; 992 | let (sn, cs) = an.sin_cos(); 993 | 994 | let n2 = vec2(normal.x * cs - normal.y * sn, normal.y * cs + normal.x * sn); 995 | p[2] = tv(at + n2 * parms.thickness[index], c.transform); 996 | 997 | if self.debug_faces { 998 | cols = [gamma_srgb_to_linear(self.next_quad_colors()[0]); 3]; 999 | } 1000 | 1001 | unsafe { 1002 | self.encoder 1003 | .update_buffer( 1004 | &self.c2d.pos, 1005 | from_raw_parts(p.as_ptr() as *const PositionFormat, 3), 1006 | self.c2d.offset, 1007 | ).unwrap(); 1008 | 1009 | self.encoder 1010 | .update_buffer( 1011 | &self.c2d.color, 1012 | from_raw_parts(cols.as_ptr() as *const ColorFormat, 3), 1013 | self.c2d.offset, 1014 | ).unwrap(); 1015 | 1016 | self.c2d.offset += 3; 1017 | } 1018 | 1019 | p[1] = p[2]; 1020 | } 1021 | 1022 | if self.debug_wireframe { 1023 | p = [at, at + normal * parms.thickness[index], vec2(0.0, 0.0)]; 1024 | for i in 0..tris { 1025 | let an = angle * (i + 1) as f32; 1026 | let sn = an.sin(); 1027 | let cs = an.cos(); 1028 | 1029 | let n2 = vec2(normal.x * cs - normal.y * sn, normal.y * cs + normal.x * sn); 1030 | p[2] = at + n2 * parms.thickness[index]; 1031 | 1032 | self.line_debug([p[0], p[1]], c); 1033 | self.line_debug([p[1], p[2]], c); 1034 | self.line_debug([p[2], p[0]], c); 1035 | 1036 | p[1] = p[2]; 1037 | } 1038 | } 1039 | } 1040 | 1041 | /// Draw a debug line 1042 | fn line_debug(&mut self, line: [Vec2; 2], c: &Context) { 1043 | let (_, nn) = line_parms(line); 1044 | let nn = nn * 0.6; 1045 | 1046 | let debug_wireframe = self.debug_wireframe; // clear so add_quad does not recurse 1047 | let debug_faces = self.debug_faces; 1048 | 1049 | self.debug_wireframe = false; 1050 | self.debug_faces = false; 1051 | let cols = [self.debug_wireframe_color; 2]; 1052 | 1053 | self.add_quad( 1054 | &[line[0] - nn, line[0] + nn, line[1] - nn, line[1] + nn], 1055 | cols, 1056 | c, 1057 | ); 1058 | 1059 | self.debug_wireframe = debug_wireframe; 1060 | self.debug_faces = debug_faces; 1061 | } 1062 | 1063 | /// Check if a state change requires us to flush the primitives buffer 1064 | fn validate_state(&mut self, draw_state: &DrawState, adding: usize) { 1065 | if &self.c2d.draw_state != draw_state || self.c2d.offset + adding > BUFFER_SIZE * CHUNKS { 1066 | self.flush(); 1067 | self.c2d.draw_state = *draw_state; 1068 | } 1069 | } 1070 | 1071 | /// FLush the current buffer by committing to the encoder 1072 | fn flush(&mut self) { 1073 | use draw_state::target::Rect; 1074 | use std::u16; 1075 | 1076 | let &mut PrimGraphics { 1077 | ref mut encoder, 1078 | output_color, 1079 | output_stencil, 1080 | c2d: 1081 | &mut Prims2d { 1082 | ref mut offset, 1083 | ref mut draw_state, 1084 | ref mut pos, 1085 | ref mut color, 1086 | ref mut colored, 1087 | .. 1088 | }, 1089 | .. 1090 | } = self; 1091 | 1092 | let (pso_colored, stencil_val) = 1093 | colored.stencil_blend(draw_state.stencil, draw_state.blend); 1094 | 1095 | let scissor = match draw_state.scissor { 1096 | None => Rect { 1097 | x: 0, 1098 | y: 0, 1099 | w: u16::MAX, 1100 | h: u16::MAX, 1101 | }, 1102 | Some(r) => Rect { 1103 | x: r[0] as u16, 1104 | y: r[1] as u16, 1105 | w: r[2] as u16, 1106 | h: r[3] as u16, 1107 | }, 1108 | }; 1109 | 1110 | let data = pipe_colored::Data { 1111 | pos: pos.clone(), 1112 | color: color.clone(), 1113 | blend_target: output_color.clone(), 1114 | stencil_target: (output_stencil.clone(), (stencil_val, stencil_val)), 1115 | blend_ref: [1.0; 4], 1116 | scissor: scissor, 1117 | }; 1118 | 1119 | let slice = gfx::Slice { 1120 | instances: None, 1121 | start: 0, 1122 | end: *offset as u32, 1123 | buffer: gfx::IndexBuffer::Auto, 1124 | base_vertex: 0, 1125 | }; 1126 | encoder.draw(&slice, pso_colored, &data); 1127 | *offset = 0; 1128 | } 1129 | } 1130 | 1131 | // 1132 | // Color utilities 1133 | // 1134 | 1135 | /// Return a color as a vec4 1136 | pub fn as_vec4(c: Color) -> Vec4 { 1137 | vec4(c[0], c[1], c[2], c[3]) 1138 | } 1139 | /// Return the vec4 as a color 1140 | pub fn as_color(v: Vec4) -> Color { 1141 | [v.x, v.y, v.z, v.w] 1142 | } 1143 | 1144 | /// Simple lerp between two colors 1145 | pub fn col_lerp(t: f32, a: Vec4, b: Vec4) -> Color { 1146 | as_color(a + (b - a) * t) 1147 | } 1148 | 1149 | /// Convert an argb 32-bit color into a graphics `Color` 1150 | pub fn color_from(argb: u32) -> Color { 1151 | [ 1152 | ((argb & 0x00_ff_00_00) >> 16) as f32 / 255.0, 1153 | ((argb & 0x00_00_ff_00) >> 8) as f32 / 255.0, 1154 | (argb & 0x00_00_00_ff) as f32 / 255.0, 1155 | ((argb & 0xff_00_00_00) >> 24) as f32 / 255.0, 1156 | ] 1157 | } 1158 | 1159 | /// Transform `v` by matrix `m` 1160 | fn tv(v: Vec2, m: Matrix2d) -> Vec2 { 1161 | vec2( 1162 | (m[0][0] * v.x as f64 + m[0][1] * v.y as f64 + m[0][2]) as f32, 1163 | (m[1][0] * v.x as f64 + m[1][1] * v.y as f64 + m[1][2]) as f32, 1164 | ) 1165 | } 1166 | 1167 | /// Return `v` as a complex number 1168 | fn complex(v: Vec2) -> Complex32 { 1169 | Complex32::new(v.x, v.y) 1170 | } 1171 | 1172 | /// Get direction and normal vectors for the given line 1173 | fn line_parms(line: [Vec2; 2]) -> (Vec2, Vec2) { 1174 | let ln = vec2_norm_or_zero(line[1] - line[0]); 1175 | let nn = vec2(-ln.y, ln.x); 1176 | 1177 | (ln, nn) 1178 | } 1179 | 1180 | /// Scale the endpoints of a line segment about its center by scale[left, right] 1181 | fn line_scaled(line: [Vec2; 2], scale: [f32; 2]) -> [Vec2; 2] { 1182 | let cn = line[0] + (line[1] - line[0]) * 0.5; 1183 | [ 1184 | cn + (line[0] - cn) * scale[0], 1185 | cn + (line[1] - cn) * scale[1], 1186 | ] 1187 | } 1188 | 1189 | /// Compute the corrected version of the lines - returns a vector of corrected lines and the 1190 | /// estimated total linear length of the new set 1191 | fn corrected_lines( 1192 | lines: &[[Vec2; 2]], 1193 | scale: f32, 1194 | parm: &DrawParms, 1195 | closed: bool, 1196 | lerp_exp: Option, 1197 | ) -> (Vec<[Vec2; 2]>, f32) { 1198 | let n = lines.len(); 1199 | if n == 0 { 1200 | return (vec![], 0.0); 1201 | } 1202 | 1203 | let connected = parm.connect_subs > 0; 1204 | 1205 | // Calculate the total estimated linear length 1206 | let mut len = 0.0; 1207 | for i in 0..n { 1208 | len += (lines[i][1] - lines[i][0]).magnitude(); 1209 | } 1210 | if closed { 1211 | len += (lines[0][0] - lines[n - 1][1]).magnitude(); 1212 | } 1213 | 1214 | // Now generate corrected lines, recompute the total length as we go 1215 | let mut ls: Vec<[Vec2; 2]> = Vec::with_capacity(n); // converted lines 1216 | let mut ln = 0.0; // new total estimated linear length 1217 | 1218 | let mut l = 0.0; // current position along the initial estimated length 1219 | let thd = parm.thickness[1] - parm.thickness[0]; // linear thickness delta 1220 | 1221 | for i in 0..n { 1222 | let ll = (lines[i][1] - lines[i][0]).magnitude(); // uncorrected length 1223 | 1224 | let mut t0 = l / len; // blend factor at each end of the line 1225 | let mut t1 = (l + ll) / len; 1226 | 1227 | if let Some(lex) = lerp_exp { 1228 | t0 = t0.powf(lex); 1229 | t1 = t1.powf(lex); 1230 | } 1231 | 1232 | let th_0 = parm.thickness[0] + thd * t0; 1233 | let th_1 = parm.thickness[0] + thd * t1; 1234 | 1235 | let cl = corrected_line(lines[i], i, n, scale, [th_0, th_1]); 1236 | 1237 | ln += (cl[1] - cl[0]).magnitude(); // recalculate lengths as we go 1238 | if connected && i > 0 { 1239 | ln += (cl[0] - ls[i - 1][1]).magnitude(); 1240 | } 1241 | 1242 | ls.push(cl); 1243 | l += ll; 1244 | } 1245 | 1246 | if closed { 1247 | ln += (ls[0][0] - ls[n - 1][1]).magnitude(); 1248 | } 1249 | 1250 | (ls, ln) 1251 | } 1252 | 1253 | /// Given a `line`, its index `i`, the total number of lines `n`, the correction factor `scale` 1254 | /// and the thickness at line[0] and line[1] `thickness` calculate and return the line 1255 | /// corrected as determined by `scale` 1256 | fn corrected_line( 1257 | line: [Vec2; 2], 1258 | i: usize, 1259 | n: usize, 1260 | scale: f32, 1261 | thickness: [f32; 2], 1262 | ) -> [Vec2; 2] { 1263 | if n == 1 { 1264 | return line; // one line, no change in any mode 1265 | } 1266 | 1267 | let mut c = line; 1268 | 1269 | // If scale in (0.0..1.0] then we apply proportional scaling 1270 | if scale > 0.0 && scale <= 1.0 { 1271 | if i == 0 { 1272 | c = line_scaled(line, [1.0, scale]); 1273 | } else if i == n - 1 { 1274 | c = line_scaled(line, [scale, 1.0]); 1275 | } else { 1276 | c = line_scaled(line, [scale, scale]); 1277 | } 1278 | } else { 1279 | let v = c[1] - c[0]; 1280 | let m = v[0].hypot(v[1]); 1281 | let sz = 1.0 / m; 1282 | 1283 | if sz.is_finite() { 1284 | let l0; 1285 | let l1; 1286 | 1287 | let cn = c[0] + v * 0.5; // center of line 1288 | let vn = v * sz; // normalized vector 1289 | 1290 | if scale > 1.0 { 1291 | // absolute 1292 | l0 = m * 0.5 - f32::min(m * 0.5, scale); // apply at most half a line length 1293 | l1 = l0; 1294 | } else { 1295 | // scale <= 0.0, automatic 1296 | l0 = m * 0.5 - f32::min(m * 0.5, thickness[0] * 1.2); 1297 | l1 = m * 0.5 - f32::min(m * 0.5, thickness[1] * 1.2); 1298 | } 1299 | 1300 | if i == 0 { 1301 | c = [c[0], cn + vn * l1]; 1302 | } else if i == n - 1 { 1303 | c = [cn - vn * l0, c[1]]; 1304 | } else { 1305 | c = [cn - vn * l0, cn + vn * l1]; 1306 | } 1307 | } 1308 | } 1309 | 1310 | c 1311 | } 1312 | 1313 | /// Derive a full set of draw parms interpolating over the line segments from `parm` 1314 | fn auto_parms( 1315 | lines: &[[Vec2; 2]], 1316 | tot_len: f32, 1317 | parm: &DrawParms, 1318 | closed: bool, 1319 | lerp_exp: Option, 1320 | ) -> Vec { 1321 | let n = lines.len(); 1322 | if n == 0 { 1323 | return vec![]; 1324 | } 1325 | 1326 | let mut ps = vec![ 1327 | DrawParms { 1328 | cap: [CapStyle::None, CapStyle::None], 1329 | ..*parm 1330 | }; 1331 | n 1332 | ]; 1333 | 1334 | ps[0].cap[0] = parm.cap[0]; // preserve the end caps at beginning and end 1335 | ps[n - 1].cap[1] = parm.cap[1]; 1336 | 1337 | if !closed { 1338 | ps[n - 1].connect_subs = 0; 1339 | } 1340 | 1341 | let mut len = 0.0; 1342 | let connected = parm.connect_subs > 0; 1343 | 1344 | let c0 = as_vec4(parm.color[0]); 1345 | let c1 = as_vec4(parm.color[1]); 1346 | 1347 | for i in 0..n { 1348 | let mut t = len / tot_len; 1349 | if let Some(lex) = lerp_exp { 1350 | t = t.powf(lex); 1351 | } 1352 | 1353 | ps[i].color[0] = col_lerp(t, c0, c1); 1354 | ps[i].thickness[0] = parm.thickness[0] + (parm.thickness[1] - parm.thickness[0]) * t; 1355 | 1356 | len += (lines[i][1] - lines[i][0]).magnitude(); 1357 | 1358 | let mut t = len / tot_len; 1359 | if let Some(lex) = lerp_exp { 1360 | t = t.powf(lex); 1361 | } 1362 | 1363 | ps[i].color[1] = col_lerp(t, c0, c1); 1364 | ps[i].thickness[1] = parm.thickness[0] + (parm.thickness[1] - parm.thickness[0]) * t; 1365 | 1366 | if connected && i < n - 1 { 1367 | len += (lines[i + 1][0] - lines[i][1]).magnitude(); 1368 | } 1369 | } 1370 | 1371 | ps 1372 | } 1373 | 1374 | /// Calculate the center point of the given lines 1375 | fn fill_parms(lines: &[[Vec2; 2]], parms: &[DrawParms]) -> (Vec2, Color) { 1376 | let mut center = vec2(0.0, 0.0); 1377 | let mut color = vec4(0.0, 0.0, 0.0, 0.0); 1378 | 1379 | let n = lines.len(); 1380 | for i in 0..n { 1381 | center += lines[i][0] + lines[i][1]; 1382 | color += as_vec4(parms[i].color[0]) + as_vec4(parms[i].color[1]); 1383 | } 1384 | 1385 | ( 1386 | center / (n * 2) as f32, 1387 | gamma_srgb_to_linear(as_color(color / (n * 2) as f32)), 1388 | ) 1389 | } 1390 | 1391 | /// Get the points along cn..(q0/q1) and cn..(q2/q3) needed to make a center tri 1392 | fn towards_center(cn: Vec2, q: [Vec2; 4]) -> (Vec2, Vec2) { 1393 | ( 1394 | if (q[0] - cn).magnitude2() < (q[1] - cn).magnitude2() { 1395 | q[0] 1396 | } else { 1397 | q[1] 1398 | }, 1399 | if (q[2] - cn).magnitude2() < (q[3] - cn).magnitude2() { 1400 | q[2] 1401 | } else { 1402 | q[3] 1403 | }, 1404 | ) 1405 | } 1406 | 1407 | /// Check and correct the pivot point for the given points 1408 | fn corrected_pivot(points: [Vec2; 2], about: Vec2) -> Complex32 { 1409 | let p0c = complex(points[0] - about); 1410 | let p1c = complex(points[1] - about); 1411 | 1412 | complex(if p0c.norm() < 0.01 { 1413 | -points[1] 1414 | } else if p1c.norm() < 0.01 { 1415 | -points[0] 1416 | } else { 1417 | about 1418 | }) 1419 | } 1420 | 1421 | /// PrimGraphics custom drop ensured that any pending primitive is flushed 1422 | impl<'a, R, C> Drop for PrimGraphics<'a, R, C> 1423 | where 1424 | R: gfx::Resources, 1425 | C: gfx::CommandBuffer, 1426 | { 1427 | fn drop(&mut self) { 1428 | if self.c2d.offset > 0 { 1429 | self.flush(); 1430 | } 1431 | } 1432 | } 1433 | 1434 | // 1435 | // Customized buffers for 2d prim rendering (modified from gfx_graphics/back_end.rs) 1436 | // 1437 | use gfx; 1438 | use gfx::format::{DepthStencil, Srgba8}; 1439 | use gfx::pso::PipelineState; 1440 | use graphics::color::gamma_srgb_to_linear; 1441 | use graphics::context::Context; 1442 | use graphics::BACK_END_MAX_VERTEX_COUNT as BUFFER_SIZE; 1443 | use graphics::{draw_state, types::Color, types::Matrix2d, DrawState}; 1444 | use shader_version::glsl::GLSL; 1445 | use shader_version::{OpenGL, Shaders}; 1446 | 1447 | pub struct Prims2d { 1448 | offset: usize, 1449 | draw_state: DrawState, 1450 | pos: gfx::handle::Buffer, 1451 | color: gfx::handle::Buffer, 1452 | colored: PsoStencil>, 1453 | } 1454 | 1455 | impl Prims2d { 1456 | pub fn new(opengl: OpenGL, factory: &mut F) -> Self 1457 | where 1458 | F: gfx::Factory, 1459 | { 1460 | use gfx::state::Rasterizer; 1461 | use gfx::state::{Blend, Stencil}; 1462 | use gfx::traits::*; 1463 | use gfx::Primitive; 1464 | use shaders::colored; 1465 | 1466 | let glsl = opengl.to_glsl(); 1467 | 1468 | let colored_program = factory 1469 | .link_program( 1470 | Shaders::new() 1471 | .set(GLSL::V1_20, colored::VERTEX_GLSL_120) 1472 | .set(GLSL::V1_50, colored::VERTEX_GLSL_150_CORE) 1473 | .get(glsl) 1474 | .unwrap(), 1475 | Shaders::new() 1476 | .set(GLSL::V1_20, colored::FRAGMENT_GLSL_120) 1477 | .set(GLSL::V1_50, colored::FRAGMENT_GLSL_150_CORE) 1478 | .get(glsl) 1479 | .unwrap(), 1480 | ).unwrap(); 1481 | 1482 | let colored_pipeline = |factory: &mut F, 1483 | blend_preset: Blend, 1484 | stencil: Stencil, 1485 | color_mask: gfx::state::ColorMask| 1486 | -> PipelineState { 1487 | factory 1488 | .create_pipeline_from_program( 1489 | &colored_program, 1490 | Primitive::TriangleList, 1491 | Rasterizer::new_fill(), 1492 | pipe_colored::Init { 1493 | pos: (), 1494 | color: (), 1495 | blend_target: ("o_Color", color_mask, blend_preset), 1496 | stencil_target: stencil, 1497 | blend_ref: (), 1498 | scissor: (), 1499 | }, 1500 | ).unwrap() 1501 | }; 1502 | 1503 | let colored = PsoStencil::new(factory, colored_pipeline); 1504 | 1505 | let buffer_pos = factory 1506 | .create_buffer( 1507 | BUFFER_SIZE * CHUNKS, 1508 | gfx::buffer::Role::Vertex, 1509 | gfx::memory::Usage::Dynamic, 1510 | gfx::memory::Bind::empty(), 1511 | ).expect("Could not create `buffer_pos`"); 1512 | let buffer_color = factory 1513 | .create_buffer( 1514 | BUFFER_SIZE * CHUNKS, 1515 | gfx::buffer::Role::Vertex, 1516 | gfx::memory::Usage::Dynamic, 1517 | gfx::memory::Bind::empty(), 1518 | ).expect("Could not create `buffer_color`"); 1519 | 1520 | Prims2d { 1521 | offset: 0, 1522 | draw_state: Default::default(), 1523 | pos: buffer_pos, 1524 | color: buffer_color, 1525 | colored: colored, 1526 | } 1527 | } 1528 | } 1529 | 1530 | const CHUNKS: usize = 100; 1531 | 1532 | gfx_defines! { 1533 | vertex PositionFormat { 1534 | pos: [f32; 2] = "pos", 1535 | } 1536 | 1537 | vertex ColorFormat { 1538 | color: [f32; 4] = "color", 1539 | } 1540 | } 1541 | 1542 | gfx_pipeline_base!( pipe_colored { 1543 | pos: gfx::VertexBuffer, 1544 | color: gfx::VertexBuffer, 1545 | blend_target: gfx::BlendTarget, 1546 | stencil_target: gfx::StencilTarget, 1547 | blend_ref: gfx::BlendRef, 1548 | scissor: gfx::Scissor, 1549 | }); 1550 | 1551 | struct PsoBlend { 1552 | alpha: T, 1553 | add: T, 1554 | multiply: T, 1555 | invert: T, 1556 | none: T, 1557 | } 1558 | 1559 | impl PsoBlend { 1560 | fn blend(&mut self, blend: Option) -> &mut T { 1561 | use graphics::draw_state::Blend; 1562 | 1563 | match blend { 1564 | Some(Blend::Alpha) => &mut self.alpha, 1565 | Some(Blend::Add) => &mut self.add, 1566 | Some(Blend::Multiply) => &mut self.multiply, 1567 | Some(Blend::Invert) => &mut self.invert, 1568 | None => &mut self.none, 1569 | } 1570 | } 1571 | } 1572 | 1573 | struct PsoStencil { 1574 | none: PsoBlend, 1575 | clip: PsoBlend, 1576 | inside: PsoBlend, 1577 | outside: PsoBlend, 1578 | } 1579 | 1580 | impl PsoStencil { 1581 | fn new(factory: &mut Fact, f: F) -> PsoStencil 1582 | where 1583 | F: Fn(&mut Fact, gfx::state::Blend, gfx::state::Stencil, gfx::state::ColorMask) -> T, 1584 | { 1585 | use gfx::preset::blend; 1586 | use gfx::state::{Blend, BlendChannel, Comparison, Equation, Factor, Stencil, StencilOp}; 1587 | 1588 | let stencil = Stencil::new( 1589 | Comparison::Always, 1590 | 0, 1591 | (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), 1592 | ); 1593 | let stencil_clip = Stencil::new( 1594 | Comparison::Never, 1595 | 255, 1596 | (StencilOp::Replace, StencilOp::Keep, StencilOp::Keep), 1597 | ); 1598 | let stencil_inside = Stencil::new( 1599 | Comparison::Equal, 1600 | 255, 1601 | (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), 1602 | ); 1603 | let stencil_outside = Stencil::new( 1604 | Comparison::NotEqual, 1605 | 255, 1606 | (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), 1607 | ); 1608 | 1609 | // Channel color masks. 1610 | let mask_all = gfx::state::ColorMask::all(); 1611 | let mask_none = gfx::state::ColorMask::empty(); 1612 | 1613 | // Fake disabled blending using the same pipeline. 1614 | let no_blend = Blend { 1615 | color: BlendChannel { 1616 | equation: Equation::Add, 1617 | source: Factor::One, 1618 | destination: Factor::Zero, 1619 | }, 1620 | alpha: BlendChannel { 1621 | equation: Equation::Add, 1622 | source: Factor::One, 1623 | destination: Factor::Zero, 1624 | }, 1625 | }; 1626 | 1627 | PsoStencil { 1628 | none: PsoBlend { 1629 | alpha: f(factory, blend::ALPHA, stencil, mask_all), 1630 | add: f(factory, blend::ADD, stencil, mask_all), 1631 | multiply: f(factory, blend::MULTIPLY, stencil, mask_all), 1632 | invert: f(factory, blend::INVERT, stencil, mask_all), 1633 | none: f(factory, no_blend, stencil, mask_all), 1634 | }, 1635 | clip: PsoBlend { 1636 | alpha: f(factory, blend::ALPHA, stencil_clip, mask_none), 1637 | add: f(factory, blend::ADD, stencil_clip, mask_none), 1638 | multiply: f(factory, blend::MULTIPLY, stencil_clip, mask_none), 1639 | invert: f(factory, blend::INVERT, stencil_clip, mask_none), 1640 | none: f(factory, no_blend, stencil_clip, mask_none), 1641 | }, 1642 | inside: PsoBlend { 1643 | alpha: f(factory, blend::ALPHA, stencil_inside, mask_all), 1644 | add: f(factory, blend::ADD, stencil_inside, mask_all), 1645 | multiply: f(factory, blend::MULTIPLY, stencil_inside, mask_all), 1646 | invert: f(factory, blend::INVERT, stencil_inside, mask_all), 1647 | none: f(factory, no_blend, stencil_inside, mask_all), 1648 | }, 1649 | outside: PsoBlend { 1650 | alpha: f(factory, blend::ALPHA, stencil_outside, mask_all), 1651 | add: f(factory, blend::ADD, stencil_outside, mask_all), 1652 | multiply: f(factory, blend::MULTIPLY, stencil_outside, mask_all), 1653 | invert: f(factory, blend::INVERT, stencil_outside, mask_all), 1654 | none: f(factory, no_blend, stencil_outside, mask_all), 1655 | }, 1656 | } 1657 | } 1658 | 1659 | // Returns a PSO and stencil reference given a stencil and blend setting. 1660 | fn stencil_blend( 1661 | &mut self, 1662 | stencil: Option, 1663 | blend: Option, 1664 | ) -> (&mut T, u8) { 1665 | use graphics::draw_state::Stencil; 1666 | 1667 | match stencil { 1668 | None => (self.none.blend(blend), 0), 1669 | Some(Stencil::Clip(val)) => (self.clip.blend(blend), val), 1670 | Some(Stencil::Inside(val)) => (self.inside.blend(blend), val), 1671 | Some(Stencil::Outside(val)) => (self.outside.blend(blend), val), 1672 | } 1673 | } 1674 | } 1675 | --------------------------------------------------------------------------------