├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── FiraMono-Medium.ttf │ ├── FiraSans-Bold.ttf │ ├── Roboto-Bold.ttf │ └── Roboto-Regular.ttf └── shaders │ ├── canvas.wgsl │ └── markers.wgsl ├── bevy_plot3.png ├── examples ├── README.md ├── animate.rs ├── bevy.rs ├── bevy0.png ├── func.rs ├── markers.rs ├── minimal.rs └── runtime_setter.rs ├── src ├── .ctxt ├── bezier │ ├── bezier.rs │ ├── bezier_backup.rs │ ├── bezier_render_backup.wgsl │ ├── bezier_spline.wgsl │ └── mod.rs ├── bezier2 │ ├── mod.rs │ └── segments.rs ├── canvas │ ├── Roboto-Bold.ttf │ ├── canvas.wgsl │ ├── canvas_actions.rs │ └── mod.rs ├── inputs.rs ├── lib.rs ├── main2.rs ├── markers │ ├── markers.wgsl │ └── mod.rs ├── plot │ ├── colors.rs │ ├── mod.rs │ ├── plot.rs │ └── plot_format.rs ├── segments │ ├── mod.rs │ ├── segments.rs │ └── segments.wgsl └── util.rs └── stiched.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Generated by Cargo 3 | # will have compiled files and executables 4 | /target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | .cargo/config.toml -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_plot" 3 | version = "0.1.6" 4 | edition = "2021" 5 | authors = ["Eliot Bolduc"] 6 | description = "A Bevy plugin for plotting data and explicit functions." 7 | license = "MIT OR Apache-2.0" 8 | readme = "README.md" 9 | keywords = ["bevy", "plot", "curve", "graph", "gamedev"] 10 | repository = "https://github.com/eliotbo/bevy_plot" 11 | categories = [ 12 | "visualization", 13 | "game-engines", 14 | "game-development", 15 | "mathematics", 16 | "science", 17 | ] 18 | 19 | 20 | [dependencies] 21 | bevy = { version = "0.15.1", default-features = false, features = [ 22 | "bevy_render", 23 | "x11", 24 | "bevy_asset", 25 | "file_watcher", 26 | "multi_threaded", 27 | "bevy_sprite", 28 | "bevy_picking", 29 | "bevy_text", 30 | "bevy_window", 31 | 32 | # "bevy_sprite_picking_backend", 33 | ] } 34 | # itertools-num = "0.1" 35 | # bytemuck = "1.21" 36 | rand = "0.8" 37 | bevy_framepace = "0.18" 38 | # bevy_picking = { version = "0.15.0-rc.3" } # why do we need this? 39 | # bevy_text = { version = "0.15.0" } 40 | # crevice = "0.11" 41 | 42 | [features] 43 | default = [] 44 | unstable = [] 45 | 46 | # [[example]] 47 | # name = "bevy" 48 | # path = "examples/bevy.rs" 49 | 50 | # [[example]] 51 | # name = "markers" 52 | # path = "examples/markers.rs" 53 | 54 | # [[example]] 55 | # name = "minimal" 56 | # path = "examples/minimal.rs" 57 | 58 | # [[example]] 59 | # name = "func" 60 | # path = "examples/func.rs" 61 | 62 | # [[example]] 63 | # name = "animate" 64 | # path = "examples/animate.rs" 65 | 66 | # [[example]] 67 | # name = "runtime_setter" 68 | # path = "examples/runtime_setter.rs" 69 | 70 | # [[example]] 71 | # name = "gege" 72 | # path = "examples/gege.rs" 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | bevy_plot is dual-licensed under either 2 | 3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT) 4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![logo](bevy_plot3.png) 3 | 4 | Plotting library for the Bevy game engine with a focus on esthetics and interactivity. It can handle both data points (see the "minimal", "markers", and "bevy" examples) and explicit functions (see the "func", "animate" and "runtime_setter" examples). Explicit functions are rendered using quadratic Bezier interpolation, thus smoothing out the curves. 5 | 6 | Here is a link to the [docs](https://docs.rs/bevy_plot/0.1.3/bevy_plot/). 7 | 8 | ![animate](stiched.gif) 9 | 10 | ## How to get started 11 | 12 | Add "bevy_plot" to the dependencies list in the Cargo.toml file of your project, add a font to your assets, and have a look at the [examples](https://github.com/eliotbo/bevy_plot/tree/main/examples) to see how to add the PlotPlugin, import and use the Plot asset. 13 | 14 | ## TODO 15 | 16 | - reduce API boilerplate 17 | - interactive markers 18 | - compatibility with 3d camera 19 | - optimization 20 | -------------------------------------------------------------------------------- /assets/fonts/FiraMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/assets/fonts/FiraMono-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/assets/fonts/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /assets/shaders/canvas.wgsl: -------------------------------------------------------------------------------- 1 | 2 | // #import bevy_sprite::mesh2d_types 3 | // #import bevy_sprite::mesh2d_view_types 4 | 5 | #import bevy_sprite::mesh2d_vertex_output::VertexOutput 6 | #import bevy_render::view::View 7 | 8 | // struct Vertex { 9 | // @location(0) position: vec3, 10 | // @location(1) normal: vec3, 11 | // @location(2) uv: vec2, 12 | // #ifdef VERTEX_TANGENTS 13 | // @location(3) tangent: vec4, 14 | // #endif 15 | // }; 16 | 17 | // struct VertexOutput { 18 | // @builtin(position) clip_position: vec4, 19 | // @location(0) world_position: vec4, 20 | // @location(1) world_normal: vec3, 21 | // @location(2) uv: vec2, 22 | // #ifdef VERTEX_TANGENTS 23 | // @location(3) world_tangent: vec4, 24 | // #endif 25 | // }; 26 | 27 | 28 | struct GraphEditShader { 29 | mouse_pos: vec2, 30 | tick_period: vec2, 31 | bound_up: vec2, // TODO 32 | bound_lo: vec2, 33 | time: f32, 34 | zoom: f32, 35 | size: vec2, 36 | outer_border: vec2, 37 | // position: vec2, 38 | show_target: f32, 39 | hide_contour: f32, 40 | target_pos: vec2, 41 | background_color1: vec4, 42 | background_color2: vec4, 43 | target_color: vec4, 44 | show_grid: f32, 45 | show_axes: f32, 46 | }; 47 | 48 | struct GraphPosition { 49 | position: vec2, 50 | }; 51 | 52 | 53 | @group(0) @binding(0) 54 | var view: View; 55 | 56 | @group(2) @binding(0) 57 | var mate: GraphEditShader; 58 | 59 | @group(2) @binding(1) var graph_position: GraphPosition; 60 | 61 | // @group(2) @binding(0) 62 | // var mesh: Mesh2d; 63 | 64 | // struct VertexOutput { 65 | // @builtin(position) clip_position: vec4, 66 | // @location(0) uv: vec2, 67 | // }; 68 | 69 | struct Vertex { 70 | @location(0) position: vec3, 71 | @location(1) normal: vec3, 72 | @location(2) uv: vec2, 73 | #ifdef VERTEX_TANGENTS 74 | @location(3) tangent: vec4, 75 | #endif 76 | }; 77 | 78 | 79 | struct Segment { 80 | start: vec2, 81 | end: vec2, 82 | }; 83 | 84 | struct Interval { 85 | start: vec2, 86 | end: vec2, 87 | control: vec4, 88 | }; 89 | 90 | struct GraphSize { 91 | size: vec2, 92 | outer_border: vec2, 93 | }; 94 | 95 | 96 | 97 | var solid: f32 = 0.001; 98 | var smooth_dist2: f32 = 0.003; 99 | var point2_radius: f32 = 0.03; 100 | var out_of_bounds: f32 = 100000.0; 101 | var bluish : vec4 = vec4(0.13, 0.28, 0.86, 1.0); 102 | var num_segments: i32 = 256; 103 | 104 | 105 | 106 | struct Globals { 107 | time: f32, 108 | zoom: f32, 109 | dum1: f32, 110 | dum2: f32, 111 | }; 112 | 113 | 114 | 115 | 116 | @vertex 117 | fn vertex(vertex: Vertex) -> VertexOutput { 118 | var out: VertexOutput; 119 | let world_position = vec4(vertex.position, 1.0); 120 | let view_proj = view.clip_from_view * view.view_from_world; 121 | out.position = view_proj * world_position; 122 | out.world_position = world_position; 123 | out.world_normal = vertex.normal; 124 | out.uv = vertex.uv; 125 | 126 | #ifdef VERTEX_TANGENTS 127 | out.world_tangent = vec4(vertex.tangent.xyz, vertex.tangent.w); 128 | #endif 129 | 130 | return out; 131 | } 132 | 133 | // struct FragmentInput { 134 | // @builtin(front_facing) is_front: bool, 135 | // @location(0) world_position: vec4, 136 | // @location(1) world_normal: vec3, 137 | // @location(2) uv: vec2, 138 | // #ifdef VERTEX_TANGENTS 139 | // @location(3) world_tangent: vec4, 140 | // #endif 141 | // }; 142 | 143 | fn from_pix_to_local(uv_orig: vec2) -> vec2 { 144 | 145 | var uv = (uv_orig - graph_position.position) ; 146 | 147 | let x_max = mate.bound_up.x; 148 | let y_max = mate.bound_up.y; 149 | 150 | let x_min = mate.bound_lo.x; 151 | let y_min = mate.bound_lo.y; 152 | 153 | let x_range = x_max - x_min; 154 | let y_range = y_max - y_min; 155 | 156 | uv.x = uv.x * (1.0 + mate.outer_border.x) / mate.size.x ; 157 | uv.x = uv.x * x_range ; 158 | 159 | uv.y = uv.y * (1.0 + mate.outer_border.y) / mate.size.y; 160 | uv.y = uv.y * y_range; 161 | 162 | let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 163 | let uv_local = uv + current_zero_pos; 164 | 165 | return uv_local; 166 | } 167 | 168 | // fn from_pix_to_local(uv_orig: vec2) -> vec2 { 169 | 170 | // // var uv = (uv_orig - graph_position.position) ; 171 | // var uv = (uv_orig - vec2(0.5, 0.5)) ; 172 | 173 | // let x_max = mate.bound_up.x; 174 | // let y_max = mate.bound_up.y; 175 | 176 | // let x_min = mate.bound_lo.x; 177 | // let y_min = mate.bound_lo.y; 178 | 179 | // let x_range = x_max - x_min; 180 | // let y_range = y_max - y_min; 181 | 182 | // uv.x = uv.x * (1.0 + mate.outer_border.x) ; 183 | // uv.x = uv.x * x_range ; 184 | 185 | // uv.y = uv.y * (1.0 + mate.outer_border.y); 186 | // uv.y = -uv.y * y_range; 187 | 188 | // let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 189 | // let uv_local = uv + current_zero_pos; 190 | 191 | // return uv_local; 192 | // } 193 | 194 | fn from_local_to_pixels(uv_local: vec2) -> vec2 { 195 | var uv = uv_local; 196 | 197 | uv.x = uv.x * mate.size.x / (1.0 + mate.outer_border.x) ; 198 | uv.x = uv.x / (mate.bound_up.x - mate.bound_lo.x); 199 | 200 | uv.y = uv.y * mate.size.y / (1.0 + mate.outer_border.y) ; 201 | uv.y = uv.y / (mate.bound_up.y - mate.bound_lo.y); 202 | 203 | 204 | 205 | return uv; 206 | } 207 | 208 | fn from_local_to_pixels3_inv(uv_local: vec2) -> vec2 { 209 | var uv = uv_local; 210 | 211 | uv.x = (uv.x - 0.5) * mate.size.x ; 212 | // uv.x = uv.x / (mate.bound_up.x - mate.bound_lo.x); 213 | 214 | uv.y = (uv.y - 0.5) * mate.size.y ; 215 | // uv.y = uv.y / (mate.bound_up.y - mate.bound_lo.y); 216 | 217 | uv = uv + graph_position.position / 2.0; 218 | 219 | 220 | 221 | return uv; 222 | } 223 | 224 | 225 | fn from_local_to_pixels3(uv_local: vec2) -> vec2 { 226 | var uv = uv_local; 227 | 228 | uv.x = (uv.x - 0.5) * mate.size.x ; 229 | // uv.x = uv.x / (mate.bound_up.x - mate.bound_lo.x); 230 | 231 | uv.y = -(uv.y - 0.5) * mate.size.y ; 232 | // uv.y = uv.y / (mate.bound_up.y - mate.bound_lo.y); 233 | 234 | uv = uv + graph_position.position / 2.0; 235 | 236 | 237 | 238 | return uv; 239 | } 240 | 241 | // fn from_uv_to_pixels2(uv_local: vec2) -> vec2 { 242 | // var uv = uv_local; 243 | 244 | // let x_max = mate.bound_up.x; 245 | // let y_max = mate.bound_up.y; 246 | 247 | // let x_min = mate.bound_lo.x; 248 | // let y_min = mate.bound_lo.y; 249 | 250 | // let x_range = x_max - x_min; 251 | // let y_range = y_max - y_min; 252 | 253 | // let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 254 | // uv = uv - current_zero_pos; 255 | 256 | // uv.y = uv.y / y_range; 257 | // uv.y = uv.y / (1.0 + mate.outer_border.y) * mate.size.y; 258 | 259 | // uv.x = uv.x / x_range; 260 | // uv.x = uv.x / (1.0 + mate.outer_border.x) * mate.size.x; 261 | 262 | // return uv; 263 | // } 264 | 265 | // // There are currently no function for x % 2 in wgpu 266 | // fn even(uv: f32) -> f32 { 267 | // // var tempo: f32 = 0.0; 268 | // // let whatever = modf(uv + 1.0, &tempo); 269 | // // var temp2 = 0.; 270 | // // let frac = modf(tempo / 2.0, &temp2); 271 | 272 | // // if abs(frac) < 0.001 { 273 | // // return 1.0; 274 | // // } else { 275 | // // return 0.0; 276 | // // } 277 | 278 | // return 1.0; 279 | // } 280 | 281 | fn even(uv: f32) -> f32 { 282 | let whole = floor(uv + 1.0); 283 | return select(0.0, 1.0, abs((whole / 2.0) % 1.0) < 0.001); 284 | } 285 | 286 | 287 | //////////////////////// sdfs ////////////////////////////////////// 288 | 289 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 290 | var x = r.x; 291 | var y = r.y; 292 | x = select(r.z, r.x, p.x > 0.); 293 | y = select(r.w, r.y, p.x > 0.); 294 | x = select(y, x, p.y > 0.); 295 | let q = abs(p) - b + x; 296 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 297 | } 298 | 299 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 300 | let pa: vec2 = p - a; 301 | let ba: vec2 = b - a; 302 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); 303 | return length(pa - ba * h); 304 | } 305 | 306 | // fn draw_segment(thickness: f32, rect: vec4, uv: vec2, segment: Segment, color: vec4, alpha: f32 ) -> vec4 { 307 | // let t = thickness; 308 | // let d = sdSegment(uv, segment.start, segment.end); 309 | // let seg_start = smoothstep(t, t + 1.0/100.0, d); 310 | // let rect2 = mix(rect, color, alpha*abs( 1.0 -seg_start)); 311 | // return rect2; 312 | // } 313 | 314 | fn draw_segment(thickness: f32, rect: vec4, uv: vec2, segment: Segment, color: vec4, alpha: f32) -> vec4 { 315 | // let uv = from_local_to_pixels(uv_orig); 316 | let t = thickness; // * mate.globals.zoom; 317 | let d = sdSegment(uv, segment.start, segment.end); 318 | let seg_start = smoothstep(t, t + t * 2.0, d); 319 | let rect2 = mix(rect, color, alpha * abs(1.0 - seg_start)); 320 | return rect2; 321 | } 322 | 323 | 324 | fn sdCircle(pos: vec2, r: f32) -> f32 { 325 | return length(pos) - r; 326 | } 327 | 328 | fn draw_circle( 329 | rect: vec4, 330 | uv_orig: vec2, 331 | r: f32, 332 | pcolor: vec4, 333 | annular: bool, 334 | point2: vec2, 335 | ) -> vec4 { 336 | 337 | let t = solid * 100.0; 338 | let s = smooth_dist2 * 200.0; 339 | 340 | let uv_pixels = from_local_to_pixels(uv_orig - point2); 341 | let r_pixels_vec2 = from_local_to_pixels(vec2(r, r)); 342 | 343 | var sd_start = sdCircle(uv_pixels, r_pixels_vec2.x); 344 | // 345 | if annular { 346 | sd_start = abs(sd_start); 347 | } 348 | let cerc_start = smoothstep(t, t + s * 2., sd_start); 349 | let rect2 = mix(rect, pcolor, 1.0 - cerc_start); 350 | return rect2; 351 | } 352 | //////////////////////// sdfs ////////////////////////////////////// 353 | 354 | 355 | 356 | 357 | 358 | @fragment 359 | fn fragment(in: VertexOutput) -> @location(0) vec4 { 360 | 361 | 362 | 363 | // ///////////////////// coordinates ///////////////////////////////// 364 | let x_max = mate.bound_up.x; 365 | let y_max = mate.bound_up.y; 366 | 367 | let x_min = mate.bound_lo.x; 368 | let y_min = mate.bound_lo.y; 369 | 370 | let x_range = x_max - x_min; 371 | let y_range = y_max - y_min; 372 | // ///////////////////// coordinates ///////////////////////////////// 373 | 374 | let uv_pix = from_local_to_pixels3(in.uv); 375 | // let uv_pix_inv = from_local_to_pixels3_inv(in.uv); 376 | 377 | var uv = from_pix_to_local(uv_pix) ; 378 | 379 | // var uv = in.uv; 380 | // var uv = uv_pix; 381 | // var uv = in.world_position.xy; 382 | 383 | // if uv_pix.x > 0.5 { 384 | // return vec4(0.0, 0.0, 0.0, 0.0); 385 | // } 386 | 387 | 388 | 389 | ///////////////////// colors ///////////////////////////////////// 390 | let red = vec4(1.0, 0.0, 0.0, 1.0); 391 | let yellow = vec4(0.89, 0.41, 0.14, 1.0); 392 | let green = vec4(0.0, 1.0, 0.0, 1.0); 393 | let black = vec4(0.0, 0.0, 0.0, 1.0); 394 | 395 | let colBackground1 = mate.background_color1; 396 | let colBackground2 = mate.background_color2; 397 | ///////////////////// colors ///////////////////// 398 | 399 | 400 | ///////////////////// background ///////////////// 401 | let tile_freq_x: f32 = 1.0 / mate.tick_period.x; 402 | let tile_freq_y: f32 = 1.0 / mate.tick_period.y; 403 | 404 | let tiles = even((floor(tile_freq_x * uv.x) + floor(tile_freq_y * uv.y))) ; //+ even(uv.y * 5.); 405 | 406 | var rect: vec4 = mix(colBackground1, colBackground2, tiles); 407 | ///////////////////// background ///////////////// 408 | 409 | 410 | 411 | ////////////////////////////////// grid //////////////////////////////// 412 | let so = mate.size / (1.0 + mate.outer_border); 413 | let edges = vec2(0.5, 0.5) * so; 414 | 415 | var origin = (-mate.bound_lo / (mate.bound_up - mate.bound_lo) - 0.5) * so; 416 | // origin.y = -origin.y; 417 | 418 | let tick_period_pix = mate.tick_period / (mate.bound_up - mate.bound_lo) * so; 419 | let bar_alpha = 1.0; 420 | 421 | var segment: Segment; 422 | 423 | var sig = sign(uv); 424 | sig = vec2(1.0, 1.0); 425 | 426 | // in the tiki coordinate, 1 corresponds to one tick period 427 | let tiki = (uv_pix - graph_position.position - origin) / tick_period_pix - vec2(0.5, 0.5) * sig ; 428 | 429 | // In wgpu currently, the mod function take a reference to a dummy variable. 430 | // This will change in the future. 431 | var temp_y: f32 = 0.0; 432 | var temp_x: f32 = 0.0; 433 | // let sad_x = modf(tiki.x, &temp_x); 434 | // let sad_y = modf(tiki.y, &temp_y); 435 | let sad_x = tiki.x % 1.0; 436 | let sad_y = tiki.y % 1.0; 437 | 438 | 439 | let ggg = -vec2(sad_x, sad_y) ; 440 | 441 | 442 | let half = -0.5 * sig; 443 | 444 | let aspect_ratio = tick_period_pix.x / tick_period_pix.y; 445 | 446 | let bars_thickness = 0.5 / tick_period_pix ; 447 | 448 | if mate.show_grid > 0.5 { 449 | // horizontal bars 450 | segment.start = vec2(-edges.x, half.y) ; 451 | segment.end = vec2(edges.x, half.y) ; 452 | rect = draw_segment(bars_thickness.y, rect, ggg, segment, black, bar_alpha) ; 453 | 454 | // vertical bars 455 | segment.start = vec2(half.x, -edges.y) ; 456 | segment.end = vec2(half.x, edges.y) ; 457 | rect = draw_segment(bars_thickness.x, rect, ggg, segment, black, bar_alpha) ; 458 | } 459 | /////////////////////////////////////// grid ///////////////////////////////////// 460 | 461 | 462 | 463 | /////////////////////////////////////// axes ////////////////////////////// 464 | if mate.show_axes > 0.5 { 465 | segment.start = vec2(-edges.x, origin.y); 466 | segment.end = vec2(edges.x, origin.y); 467 | rect = draw_segment(1.0, rect, uv_pix - graph_position.position, segment, black, bar_alpha) ; 468 | 469 | 470 | segment.start = vec2(origin.x, -edges.y); 471 | segment.end = vec2(origin.x, edges.y); 472 | rect = draw_segment(1.0, rect, uv_pix - graph_position.position, segment, black, bar_alpha) ; 473 | } 474 | //////////////////////////////////////// axes ////////////////////////////// 475 | 476 | 477 | 478 | /////////////////// borders ///////////////////////// 479 | rect = mix(rect, colBackground2, step(x_max, uv.x)); 480 | rect = mix(rect, colBackground2, step(-x_min, -uv.x)); 481 | rect = mix(rect, colBackground2, step(-y_min, -uv.y)); 482 | rect = mix(rect, colBackground2, step(y_max, uv.y)); 483 | /////////////////// borders ///////////////////////// 484 | 485 | 486 | /////////////////// mouse target ///////////////////////// 487 | if mate.show_target > 0.5 { 488 | // let aspect_ratio = mate.size.y / mate.size.x; 489 | 490 | let target_thickness = 0.75; // mate.globals.zoom; 491 | let pos_edges = edges - graph_position.position; 492 | 493 | segment.start = vec2(mate.target_pos.x, -pos_edges.y); 494 | segment.end = vec2(mate.target_pos.x, pos_edges.y); 495 | rect = draw_segment(target_thickness, rect, uv_pix, segment, mate.target_color, bar_alpha); 496 | 497 | segment.start = vec2(-pos_edges.x, mate.target_pos.y); 498 | segment.end = vec2(pos_edges.x, mate.target_pos.y); 499 | rect = draw_segment(target_thickness, rect, uv_pix, segment, mate.target_color, bar_alpha); 500 | } 501 | /////////////////// mouse target ///////////////////////// 502 | 503 | 504 | /////////////////// contours ///////////////////////// 505 | if mate.hide_contour < 0.5 { 506 | 507 | let so = mate.size / (1.0 + mate.outer_border); 508 | let ax_thick = 0.8 ; 509 | 510 | let r = 0.02 * so.x; 511 | let d = sdRoundedBox(uv_pix - graph_position.position, so / 2.0, vec4(r, r, r, r)); 512 | let s = smoothstep(0.0, 2.0, d); 513 | 514 | let colBackground3 = vec4(colBackground2.xyz, 0.0); 515 | rect = mix(rect, colBackground3, s); 516 | 517 | let r2 = 0.02 * so.x; 518 | let d2 = sdRoundedBox(uv_pix - graph_position.position, so / 2.0, vec4(r2, r2, r2, r2)); 519 | let s2 = smoothstep(0.0, 2.0, abs(d2) - 1.0); 520 | 521 | rect = mix(rect, vec4(0.0, 0.0, 0.0, 1.0), 1.0 - s2); 522 | } 523 | /////////////////// contours ///////////////////////// 524 | 525 | 526 | 527 | return rect; 528 | } 529 | -------------------------------------------------------------------------------- /assets/shaders/markers.wgsl: -------------------------------------------------------------------------------- 1 | // // Import the standard 2d mesh uniforms and set their bind groups 2 | // #import bevy_sprite::mesh2d_view_bind_group 3 | 4 | // [[group(0), binding(0)]] 5 | // var view: View; 6 | 7 | 8 | // #import bevy_sprite::mesh2d_struct 9 | 10 | // [[group(2), binding(0)]] 11 | // var mesh: Mesh2d; 12 | 13 | #import bevy_sprite::mesh2d_bindings 14 | #import bevy_sprite::mesh2d_view_bindings 15 | 16 | 17 | 18 | // // The structure of the vertex buffer is as specified in `specialize()` 19 | // struct Vertex { 20 | // [[location(0)]] position: vec3; 21 | // [[location(1)]] normal: vec3; 22 | // [[location(2)]] uv: vec2; 23 | 24 | // // instanced 25 | // [[location(3)]] i_pos_scale: vec4; 26 | // [[location(4)]] i_color: vec4; 27 | // }; 28 | 29 | struct Vertex { 30 | @location(0) position: vec3, 31 | @location(1) normal: vec3, 32 | @location(2) uv: vec2, 33 | @location(3) i_pos_scale: vec4, 34 | @location(4) i_color: vec4, 35 | }; 36 | 37 | struct MarkerUniform { 38 | marker_size: f32, 39 | hole_size: f32, 40 | zoom: f32, 41 | point_type: i32, 42 | quad_size: f32, 43 | contour: f32, 44 | inner_canvas_size_in_pixels: vec2, 45 | canvas_position_in_pixels: vec2, 46 | color: vec4, 47 | marker_point_color: vec4, 48 | 49 | }; 50 | 51 | @group(1) @binding(0) 52 | var uni: MarkerUniform; 53 | 54 | // struct VertexOutput { 55 | // // The vertex shader must set the on-screen position of the vertex 56 | // [[builtin(position)]] clip_position: vec4; 57 | 58 | // [[location(0)]] uv: vec2; 59 | // [[location(1)]] pos_scale: vec4; 60 | // [[location(2)]] color: vec4; 61 | // }; 62 | 63 | struct VertexOutput { 64 | @builtin(position) clip_position: vec4, 65 | @location(0) uv: vec2, 66 | @location(1) pos_scale: vec4, 67 | @location(2) color: vec4, 68 | }; 69 | 70 | @vertex 71 | fn vertex(vertex: Vertex) -> VertexOutput { 72 | 73 | let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz ; 74 | let world_position = mesh.model * vec4(position, 1.0); 75 | 76 | var out: VertexOutput; 77 | 78 | out.clip_position = view.view_proj * world_position; 79 | out.color = vertex.i_color; 80 | out.uv = vertex.uv; 81 | out.pos_scale = vertex.i_pos_scale; 82 | 83 | return out; 84 | } 85 | 86 | fn fromLinear(linearRGB: vec4) -> vec4 { 87 | let cutoff: vec4 = vec4(linearRGB < vec4(0.0031308)); 88 | let higher: vec4 = vec4(1.055) * pow(linearRGB, vec4(1.0 / 2.4)) - vec4(0.055); 89 | let lower: vec4 = linearRGB * vec4(12.92); 90 | 91 | return mix(higher, lower, cutoff); 92 | } 93 | 94 | // Converts a color from sRGB gamma to linear light gamma 95 | fn toLinear(sRGB: vec4) -> vec4 { 96 | let cutoff = vec4(sRGB < vec4(0.04045)); 97 | let higher = pow((sRGB + vec4(0.055)) / vec4(1.055), vec4(2.4)); 98 | let lower = sRGB / vec4(12.92); 99 | 100 | return mix(higher, lower, cutoff); 101 | } 102 | 103 | 104 | // struct FragmentInput { 105 | // [[location(0)]] uv: vec2; 106 | // [[location(1)]] pos_scale: vec4; 107 | // [[location(2)]] color: vec4; 108 | // }; 109 | 110 | struct FragmentInput { 111 | @location(0) uv: vec2, 112 | @location(1) pos_scale: vec4, 113 | @location(2) color: vec4, 114 | }; 115 | 116 | 117 | fn cla(mi: f32, ma: f32, x: f32) -> f32 { 118 | if x < mi { 119 | return mi; 120 | } 121 | if x > ma { 122 | return ma; 123 | } 124 | return x; 125 | } 126 | 127 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 128 | let pa = p - a; 129 | let ba = b - a; 130 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.); 131 | return length(pa - ba * h); 132 | } 133 | 134 | fn sdRhombus(p: vec2, b: vec2) -> f32 { 135 | let q = abs(p); 136 | let qb = dot(q, vec2(b.x, -b.y)); 137 | let bb = dot(b, vec2(b.x, -b.y)); 138 | let h = clamp((-2. * qb + bb) / dot(b, b), -1., 1.); 139 | let d = length(q - 0.5 * b * vec2(1. - h, 1. + h)); 140 | return d * sign(q.x * b.y + q.y * b.x - b.x * b.y); 141 | } 142 | 143 | fn sdTriangleIsosceles(p: vec2, c: vec2) -> f32 { 144 | let q = vec2(abs(p.x), p.y); 145 | let a = q - c * clamp(dot(q, c) / dot(c, c), 0., 1.); 146 | let b = q - c * vec2(clamp(q.x / c.x, 0., 1.), 1.); 147 | let s = -sign(c.y); 148 | let d = min(vec2(dot(a, a), s * (q.x * c.y - q.y * c.x)), vec2(dot(b, b), s * (q.y - c.y))); 149 | return -sqrt(d.x) * sign(d.y); 150 | } 151 | 152 | fn sdStar(p: vec2, r: f32, n: u32, m: f32) -> f32 { 153 | let an = 3.141593 / f32(n); 154 | let en = 3.141593 / m; 155 | let acs = vec2(cos(an), sin(an)); 156 | let ecs = vec2(cos(en), sin(en)); 157 | let bn = (atan2(abs(p.x), p.y) % (2. * an)) - an; 158 | var q: vec2 = length(p) * vec2(cos(bn), abs(sin(bn))); 159 | q = q - r * acs; 160 | q = q + ecs * clamp(-dot(q, ecs), 0., r * acs.y / ecs.y); 161 | return length(q) * sign(q.x); 162 | } 163 | 164 | fn sdHeart(p: vec2) -> f32 { 165 | let q = vec2(abs(p.x), p.y); 166 | let w = q - vec2(0.25, 0.75); 167 | if q.x + q.y > 1.0 { return sqrt(dot(w, w)) - sqrt(2.) / 4.; } 168 | let u = q - vec2(0., 1.0); 169 | let v = q - 0.5 * max(q.x + q.y, 0.); 170 | return sqrt(min(dot(u, u), dot(v, v))) * sign(q.x - q.y); 171 | } 172 | 173 | fn sdMoon(p: vec2, d: f32, ra: f32, rb: f32) -> f32 { 174 | let q = vec2(p.x, abs(p.y)); 175 | let a = (ra * ra - rb * rb + d * d) / (2. * d); 176 | let b = sqrt(max(ra * ra - a * a, 0.)); 177 | if d * (q.x * b - q.y * a) > d * d * max(b - q.y, 0.) { return length(q - vec2(a, b)); } 178 | return max((length(q) - ra), -(length(q - vec2(d, 0.)) - rb)); 179 | } 180 | 181 | fn sdCross(p: vec2, b: vec2) -> f32 { 182 | var q: vec2 = abs(p); 183 | q = select(q.xy, q.yx, q.y > q.x); 184 | let t = q - b; 185 | let k = max(t.y, t.x); 186 | let w = select(vec2(b.y - q.x, -k), t, k > 0.); 187 | return sign(k) * length(max(w, vec2(0.))); 188 | } 189 | 190 | fn sdRoundedX(p: vec2, w: f32, r: f32) -> f32 { 191 | let q = abs(p); 192 | return length(q - min(q.x + q.y, w) * 0.5) - r; 193 | } 194 | 195 | fn sdCircle(p: vec2, c: vec2, r: f32) -> f32 { 196 | let d = length(p - c); 197 | return d - r; 198 | } 199 | 200 | 201 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 202 | var x = r.x; 203 | var y = r.y; 204 | x = select(r.z, r.x, p.x > 0.); 205 | y = select(r.w, r.y, p.x > 0.); 206 | x = select(y, x, p.y > 0.); 207 | let q = abs(p) - b + x; 208 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 209 | } 210 | 211 | fn sdBox(p: vec2, b: vec2) -> f32 { 212 | let d = (abs(p) - b) ; 213 | return length(max(d, vec2(0.))) + min(max(d.x, d.y), 0.); 214 | } 215 | 216 | 217 | 218 | 219 | 220 | @fragment 221 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 222 | 223 | let width = 0.041 ; 224 | let zoom = uni.zoom; 225 | 226 | var w = width * zoom ; 227 | var solid = width * zoom ; 228 | 229 | 230 | var out_col = uni.color; 231 | 232 | var uv = in.uv - vec2(0.5, 0.5); 233 | 234 | var uv_in_pixels = vec2(-uv.x, uv.y) * uni.quad_size - in.pos_scale.xy; 235 | 236 | let marker_size = uni.marker_size; 237 | 238 | let point_type = i32(uni.point_type); 239 | // let point_type = 6; 240 | 241 | // change the aliasing as a function of the zoom 242 | var circ_zoom = zoom; 243 | 244 | if zoom > .0 { 245 | circ_zoom = pow(zoom, 0.05); 246 | } 247 | 248 | if zoom < 1.0 { 249 | circ_zoom = sqrt(sqrt(zoom)); 250 | } 251 | 252 | // square -> 0 253 | // heart -> 1 254 | // rhombus -> 2 255 | // triangle -> 3 256 | // star -> 4 257 | // moon -> 5 258 | // cross -> 6 259 | // x -> 7 260 | // circle -> 8 261 | 262 | let black = vec4(0.0, 0.0, 0.0, 1.0); 263 | if point_type == -1 { 264 | return vec4(0.0); 265 | } else if point_type == 0 { // square -> 0 266 | 267 | let r = cla(0.01, 0.3, 0.2 * uni.marker_size); 268 | let side_size = cla(0.1, 0.45, 0.4 * uni.marker_size); 269 | 270 | let d = sdRoundedBox(uv, vec2(side_size, side_size), vec4(r, r, r, r)); 271 | let s = smoothstep(solid * 0.0, solid * 0.0 + w, d); 272 | 273 | out_col = out_col * (1.0 - s); 274 | 275 | 276 | // heart -> 1 277 | } else if point_type == 1 { 278 | uv.y = -uv.y; 279 | 280 | let heart_size = cla(0.2, 0.6, 0.15 * uni.marker_size); 281 | let w_heart = w / heart_size; 282 | 283 | let d = sdHeart((uv - vec2(0.0, -heart_size * 0.9 + 0.15)) / heart_size + vec2(0.0, 0.2)); 284 | 285 | let s = smoothstep(0.0, w_heart, d); 286 | 287 | out_col = out_col * (1.0 - s); 288 | 289 | 290 | // rhombus -> 2 291 | } else if point_type == 2 { 292 | 293 | let size = cla(0.1, 0.4, 0.3 * uni.marker_size); 294 | 295 | let d = sdRhombus(uv, vec2(size * 1.2, size * 0.8)); 296 | let s = smoothstep(0.0, w / circ_zoom, d); 297 | 298 | out_col = out_col * (1.0 - s); 299 | 300 | if uni.contour > 0.5 { 301 | let d = sdRhombus(uv, vec2(size * 1.2, size * 0.8) * 1.2); 302 | let s = smoothstep(0.0, w / circ_zoom, abs(d) - 0.02); 303 | 304 | out_col = mix(black, out_col, s); 305 | } 306 | 307 | 308 | // triangle -> 3 309 | } else if point_type == 3 { 310 | 311 | uv.y = -uv.y; 312 | 313 | let size = cla(0.13, 0.5, 0.3 * uni.marker_size); 314 | 315 | let d = sdTriangleIsosceles(uv - vec2(0.0, -size * 0.5), vec2(size * 0.7, size)); 316 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 317 | 318 | out_col = out_col * (1.0 - s); 319 | 320 | if uni.contour > 0.5 { 321 | let d = sdTriangleIsosceles(uv - vec2(0.0, -size * 0.5), vec2(size * 0.7, size)); 322 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 323 | 324 | out_col = mix(black, out_col, s); 325 | } 326 | 327 | // star -> 4 328 | } else if point_type == 4 { 329 | 330 | let star_size = cla(0.05, 0.2, 0.1 * uni.marker_size); 331 | 332 | let d = sdStar(uv, star_size, u32(5), 0.35); 333 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 334 | 335 | out_col = out_col * (1.0 - s); 336 | 337 | // let sb = smoothstep(1.0 , 0.0 + w / circ_zoom, d ); 338 | 339 | if uni.contour > 0.5 { 340 | let d = sdStar(uv, star_size, u32(5), 0.35); 341 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 342 | out_col = mix(black, out_col, s); 343 | } 344 | 345 | // moon -> 5 346 | } else if point_type == 5 { 347 | 348 | let moon_size = cla(0.3, 1.3, uni.marker_size); 349 | 350 | let d = sdMoon(uv - vec2(0.05 * (1.0 + moon_size * 0.7), 0.0), 0.3 * moon_size, 0.35 * moon_size, 0.35 * moon_size); 351 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 352 | 353 | out_col = out_col * (1.0 - s); 354 | 355 | if uni.contour > 0.5 { 356 | let d = sdMoon(uv - vec2(0.05 * (1.0 + moon_size * 0.7), 0.0), 0.3 * moon_size, 0.35 * moon_size, 0.35 * moon_size); 357 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 358 | out_col = mix(black, out_col, s); 359 | } 360 | 361 | // cross -> 6 362 | } else if point_type == 6 { 363 | 364 | let cross_size = cla(0.1, 0.4, 0.25 * uni.marker_size); 365 | 366 | let d = sdCross(uv, vec2(cross_size, cross_size / 3.0)); 367 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 368 | 369 | 370 | out_col = out_col * (1.0 - s); 371 | 372 | if uni.contour > 0.5 { 373 | let d = sdCross(uv, vec2(cross_size, cross_size / 3.0)); 374 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 375 | out_col = mix(black, out_col, s); 376 | } 377 | 378 | 379 | // x -> 7 380 | } else if point_type == 7 { 381 | let ex_size = cla(0.15, 0.6, 0.3 * uni.marker_size); 382 | 383 | let start_size = 0.1; 384 | let d = sdRoundedX(uv, ex_size, ex_size / 6.0); 385 | let s = smoothstep(0.0, w / circ_zoom, d); 386 | 387 | out_col = out_col * (1.0 - s); 388 | 389 | if uni.contour > 0.5 { 390 | let d = sdRoundedX(uv, ex_size, ex_size / 6.0); 391 | let s = smoothstep(0.0, w / circ_zoom, abs(d) - 0.02); 392 | out_col = mix(black, out_col, s); 393 | } 394 | 395 | // circles -> 8 396 | } else if point_type == 8 { 397 | 398 | let circle_size = cla(0.04, 0.45, 0.25 * uni.marker_size); 399 | 400 | let r = circle_size; 401 | let d = sdCircle(uv, vec2(0.0, 0.0), circle_size); 402 | let s = smoothstep(0.0, w, d); 403 | 404 | out_col = out_col * (1.0 - s) ; 405 | 406 | if uni.contour > 0.5 { 407 | let d = sdCircle(uv, vec2(0.0, 0.0), circle_size); 408 | let s = smoothstep(0.0, w, abs(d) - 0.02); 409 | out_col = mix(black, out_col, s); 410 | } 411 | } 412 | 413 | // tiny circle at exact location of data point 414 | let inner_circle_color = uni.marker_point_color; 415 | let dc = sdCircle(uv, vec2(0.0, 0.0), 0.025 * uni.hole_size); 416 | let sc = smoothstep(0.0, w / circ_zoom * uni.hole_size, dc); 417 | out_col = mix(out_col, inner_circle_color, 1.0 - sc) ; 418 | 419 | // mask with the canvas 420 | let r = 0.02 * uni.inner_canvas_size_in_pixels.x; 421 | let d = sdRoundedBox( 422 | uv_in_pixels + uni.canvas_position_in_pixels, 423 | uni.inner_canvas_size_in_pixels / 2.0 - 1.0, 424 | vec4(r, r, r, r) 425 | ); 426 | 427 | let s = smoothstep(0.0, 0.1, d); 428 | out_col = mix(out_col, vec4(0.0, 0.3, 0.3, 0.0), s) ; 429 | 430 | return out_col; 431 | } -------------------------------------------------------------------------------- /bevy_plot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/bevy_plot3.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The bevy.rs example should yield the following: 4 | 5 | ![bevy](bevy0.png) 6 | -------------------------------------------------------------------------------- /examples/animate.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use bevy_plot::*; 4 | 5 | use std::collections::HashMap; 6 | 7 | // BUG: Lag comes and goes depending on the zoom value. 8 | 9 | fn main() { 10 | App::new() 11 | .insert_resource(WindowDescriptor { 12 | width: 800., 13 | height: 600., 14 | ..Default::default() 15 | }) 16 | // .insert_resource(ClearColor(Color::rgba(0.0, 0.0, 0.0, 0.0))) 17 | .add_plugins(DefaultPlugins) 18 | .add_plugin(PlotPlugin) 19 | .add_startup_system(setup) 20 | .add_system(exit) 21 | .run(); 22 | } 23 | 24 | fn setup( 25 | mut commands: Commands, 26 | colors_res: Res>>, 27 | mut plots: ResMut>, 28 | ) { 29 | // commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 30 | commands.spawn_bundle(Camera2dBundle::default()); 31 | 32 | let colors = colors_res.as_ref(); 33 | 34 | let mut plot = Plot::default(); 35 | plot.canvas_size = Vec2::new(790.0, 590.0); 36 | // plot.canvas_size = Vec2::new(802.0, 602.0) / (1.0 - plot.outer_border); 37 | 38 | plot.show_axes = false; 39 | plot.show_grid = false; 40 | plot.hide_contour = true; 41 | plot.hide_tick_labels = true; 42 | 43 | // transparent background 44 | plot.background_color1 = Color::rgba(0.0, 0.0, 0.0, 0.0); 45 | plot.background_color2 = Color::rgba(0.0, 0.0, 0.0, 0.0); 46 | 47 | // number of sample points taken to plot the given function 48 | plot.bezier_num_points = 75; 49 | 50 | // extremeties of the graph axes 51 | let lower_bound = Vec2::new(-1.5, -1.0); 52 | let upper_bound = Vec2::new(3.0, 10.0); 53 | plot.set_bounds(lower_bound, upper_bound); 54 | 55 | // quadratic curve 56 | plot.plotopt_func( 57 | |x: f32, t: f32| 2.0 + x * x * (1.5 + (t * 2.0).sin()), 58 | vec![ 59 | Opt::LineStyle(LineStyle::Solid), 60 | Opt::Color(colors.get(&PlotColor::Orange).unwrap()[5]), 61 | Opt::Size(0.5), 62 | Opt::Animate(true), 63 | ], 64 | ); 65 | 66 | // sine wave 67 | plot.plotopt_func( 68 | f3, 69 | vec![ 70 | Opt::Size(2.0), 71 | Opt::Color(colors.get(&PlotColor::LightPink).unwrap()[1]), 72 | Opt::Mech(true), 73 | Opt::Animate(true), 74 | ], 75 | ); 76 | 77 | // easing function (typically used in animations) 78 | plot.plotopt_func(easing_func, vec![Opt::Animate(true)]); 79 | 80 | let plot_handle = plots.add(plot.clone()); 81 | // Dummy entity that will be deleted as soon as its purpose has been served. 82 | // Required for easy access to the plot handle when spawning the graph, 83 | // instead of building a plot handle from a weak handle which can 84 | // lead to complications. 85 | commands.spawn().insert(plot_handle); 86 | } 87 | 88 | pub fn f3(x: f32, t: f32) -> f32 { 89 | let freq = 5.0; 90 | let y = (x * freq + t * 2.0).sin() / 2.0 + 5.0; 91 | return y; 92 | } 93 | 94 | pub fn easing_func(x: f32, t: f32) -> f32 { 95 | let start_point: Vec2 = Vec2::ZERO; 96 | let end_point: Vec2 = Vec2::splat(1.0); 97 | let y_min = start_point.y; 98 | let y_max = end_point.y; 99 | 100 | // visual bug appears when the exponent is close to zero 101 | let expo: f32 = 4.1 + (t * 2.0).sin() * 3.0; 102 | 103 | let xp = (x - start_point.x) / (end_point.x - start_point.x); 104 | let mut sign = (1.0 - xp).signum(); 105 | if sign == 0.0 { 106 | sign = 1.0; 107 | } 108 | let f = y_max - sign * (1.0 - xp).abs().powf(expo) * (y_max - y_min); 109 | 110 | return f; 111 | } 112 | 113 | // a system that exist the program upon pressing q or escape 114 | fn exit(keyboard_input: Res>) { 115 | if keyboard_input.just_pressed(KeyCode::Escape) || keyboard_input.just_pressed(KeyCode::Q) { 116 | std::process::exit(0); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /examples/bevy.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_plot::*; 3 | 4 | use std::collections::HashMap; 5 | 6 | fn main() { 7 | App::new() 8 | .insert_resource(WindowDescriptor { 9 | width: 1000., 10 | height: 800., 11 | ..Default::default() 12 | }) 13 | .add_plugins(DefaultPlugins) 14 | .add_plugin(PlotPlugin) 15 | .add_startup_system(setup) 16 | .run(); 17 | } 18 | 19 | // MouseButton::Middle toggles a target with x/y labels at the position of the mouse 20 | fn setup( 21 | mut commands: Commands, 22 | colors_res: Res>>, 23 | mut plots: ResMut, 24 | asset_server: Res, 25 | mut maybe_font: ResMut, 26 | ) { 27 | commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 28 | let font: Handle = asset_server.load("fonts/Roboto-Bold.ttf"); 29 | maybe_font.maybe_font = Some(font); 30 | 31 | let mut plot = Plot::default(); 32 | 33 | plot.canvas_size = Vec2::new(777.0, 555.0); 34 | plot.canvas_position = Vec2::new(-77.0, 0.0); 35 | plot.hide_half_ticks = true; 36 | plot.significant_digits = 2; 37 | plot.show_target = true; 38 | plot.show_grid = false; 39 | 40 | let colors = colors_res.as_ref(); 41 | plot.tick_label_color = colors.get(&PlotColor::Black).unwrap()[5]; 42 | plot.background_color1 = colors.get(&PlotColor::Cream).unwrap()[1]; 43 | plot.background_color2 = colors.get(&PlotColor::Cream).unwrap()[2] * 0.8; 44 | 45 | let lower_bound = Vec2::new(-0.2, -0.2); 46 | let upper_bound = Vec2::new(1.0, 1.0); 47 | 48 | plot.set_bounds(lower_bound, upper_bound); 49 | 50 | let ys: Vec<(f32, f32)> = vec![ 51 | (0.040031026908292355, 0.4034227550744621), 52 | (0.04649615595826484, 0.4157128539082504), 53 | (0.0525282811641955, 0.4265382422638997), 54 | (0.056560406370126326, 0.4061728335857745), 55 | (0.05839319055464032, 0.43630030088215466), 56 | (0.06462465678198792, 0.4077323006482807), 57 | (0.06462465678198792, 0.44591188460841347), 58 | (0.07158923668314116, 0.45572284756656734), 59 | (0.07268890719384952, 0.40920969049697087), 60 | (0.07892037342119718, 0.46502219589193383), 61 | (0.08075315760571117, 0.4104818873111209), 62 | (0.08625151015925314, 0.47481811136088803), 63 | (0.08881740801757276, 0.41171304551836274), 64 | (0.09394920373421195, 0.4838999550696419), 65 | (0.09688165842943436, 0.4129442037256046), 66 | (0.1020134541460736, 0.4923949466996106), 67 | (0.10494590884129601, 0.4139291302913979), 68 | (0.11007770455793525, 0.5003974750466824), 69 | (0.11301015925315766, 0.41487301825028344), 70 | (0.11814195496979679, 0.5078665015039496), 71 | (0.1210744096650192, 0.41573482899535263), 72 | (0.12620620538165844, 0.514925141892136), 73 | (0.12913866007688085, 0.41643248531278965), 74 | (0.1342704557935201, 0.5214913189974257), 75 | (0.1372029104887425, 0.41717118023713473), 76 | (0.14233470620538163, 0.5277702258543591), 77 | (0.14526716090060404, 0.4177867593407556), 78 | (0.15039895661724328, 0.5335566694283957), 79 | (0.1533314113124657, 0.4180740295891121), 80 | (0.15846320702910494, 0.5391379199678921), 81 | (0.16139566172432734, 0.418648570085825), 82 | (0.16652745744096648, 0.54447293886594), 83 | (0.16945991213618888, 0.4189768789410895), 84 | (0.17459170785282813, 0.5494386103018153), 85 | (0.17165925315760572, 0.05440631061263901), 86 | (0.17147597473915427, 0.03729731539266834), 87 | (0.17459170785282813, 0.06939360985546283), 88 | (0.17679104887424496, 0.02566287033423298), 89 | (0.17752416254805053, 0.4189768789410895), 90 | (0.17935694673256447, 0.07944909451311044), 91 | (0.18265595826468978, 0.5540349342755182), 92 | (0.1818274299835257, 0.6730336707577802), 93 | (0.1848552992861065, 0.020779276112173783), 94 | (0.18558841295991219, 0.4189768789410895), 95 | (0.18632152663371776, 0.08771324397922131), 96 | (0.19072020867655132, 0.5584671038215887), 97 | (0.1924565305355646, 0.680745154512709), 98 | (0.19291954969796815, 0.020204735615460878), 99 | (0.19365266337177373, 0.4189768789410895), 100 | (0.1943857770455793, 0.09337657173253366), 101 | (0.19878445908841297, 0.6638542463614894), 102 | (0.19878445908841297, 0.5626120031193029), 103 | (0.2009838001098298, 0.6856457466296697), 104 | (0.2009838001098298, 0.020820314719081856), 105 | (0.20171691378363538, 0.4189768789410895), 106 | (0.20245002745744095, 0.09686485331971884), 107 | (0.20684870950027462, 0.6596683084568671), 108 | (0.20684870950027462, 0.5665517093824768), 109 | (0.20904805052169134, 0.6902420706033725), 110 | (0.20904805052169134, 0.021641086857243086), 111 | (0.20978116419549703, 0.41885376312036526), 112 | (0.2105142778693026, 0.09920405391347842), 113 | (0.21491295991213616, 0.6554413319453368), 114 | (0.21491295991213616, 0.5702862226111103), 115 | (0.217112300933553, 0.694592162935627), 116 | (0.217112300933553, 0.022297704567772136), 117 | (0.21784541460735857, 0.4181150681960202), 118 | (0.21857852828116414, 0.10117390704506524), 119 | (0.2229772103239978, 0.6512553940407146), 120 | (0.2229772103239978, 0.5738155428052035), 121 | (0.22517655134541464, 0.6987781008402492), 122 | (0.22517655134541464, 0.023077438099025183), 123 | (0.22590966501922022, 0.417950913768388), 124 | (0.2266427786930258, 0.10330791460428446), 125 | (0.23104146073585946, 0.6470284175291843), 126 | (0.23104146073585946, 0.5770575927509403), 127 | (0.23324080175727618, 0.7026357298896069), 128 | (0.23324080175727618, 0.023980287451002558), 129 | (0.23397391543108187, 0.41717118023713473), 130 | (0.23470702910488744, 0.10560607659113586), 131 | (0.239105711147721, 0.6428835182314702), 132 | (0.239105711147721, 0.5801354882690449), 133 | (0.24130505216913783, 0.7062881659044244), 134 | (0.24130505216913783, 0.024883136802979933), 135 | (0.2420381658429434, 0.4165145625266058), 136 | (0.24277127951674898, 0.10806839300561943), 137 | (0.24716996155958265, 0.6386155031130318), 138 | (0.24716996155958265, 0.5830492293595173), 139 | (0.24936930258099949, 0.7096943702777934), 140 | (0.24936930258099949, 0.02586806336877323), 141 | (0.25010241625480506, 0.41573482899535263), 142 | (0.25083552992861063, 0.11053070942010312), 143 | (0.2552342119714443, 0.6345116424222257), 144 | (0.2552342119714443, 0.5857167388085412), 145 | (0.257433552992861, 0.712813304402806), 146 | (0.257433552992861, 0.026935067148382896), 147 | (0.2581666666666667, 0.4147909410364673), 148 | (0.2588997803404723, 0.11315718026221899), 149 | (0.26329846238330584, 0.6302436273037874), 150 | (0.26329846238330584, 0.5881380166161168), 151 | (0.2654978034047227, 0.7157680841001866), 152 | (0.2654978034047227, 0.02812518674871667), 153 | (0.26623091707852825, 0.4137649758637657), 154 | (0.2669640307523338, 0.11594780553196715), 155 | (0.2713627127951675, 0.6260987280060732), 156 | (0.2713627127951675, 0.5905182558167843), 157 | (0.2735620538165843, 0.7185176707630265), 158 | (0.2735620538165843, 0.02931530634905055), 159 | (0.2742951674903899, 0.41241070183579975), 160 | (0.2750282811641955, 0.11882050801553146), 161 | (0.27942696320702914, 0.6218717514945429), 162 | (0.27942696320702914, 0.5926522633760035), 163 | (0.28162630422844587, 0.7210210257844183), 164 | (0.28162630422844587, 0.03046438734247625), 165 | (0.28235941790225155, 0.41122058223546587), 166 | (0.2830925315760571, 0.12189840353363612), 167 | (0.2874912136188907, 0.6176858135899207), 168 | (0.2874912136188907, 0.5946631551144985), 169 | (0.2896905546403075, 0.7233191877712697), 170 | (0.2896905546403075, 0.03198281579807438), 171 | (0.2904236683141131, 0.4096200765660516), 172 | (0.29115678198791867, 0.1250994148724648), 173 | (0.29555546403075234, 0.6134588370783904), 174 | (0.29555546403075234, 0.5964278152115452), 175 | (0.29775480505216917, 0.7253711181166728), 176 | (0.29775480505216917, 0.03329605121913237), 177 | (0.29848791872597474, 0.40806060950354517), 178 | (0.2992210323997803, 0.12842354203201767), 179 | (0.303619714442614, 0.6087530768195994), 180 | (0.303619714442614, 0.5980283208809596), 181 | (0.3058190554640307, 0.7273409712482597), 182 | (0.3058190554640307, 0.03477344106782265), 183 | (0.3065521691378363, 0.40621387219268246), 184 | (0.30728528281164197, 0.13195286222611102), 185 | (0.3116839648544755, 0.604102526551825), 186 | (0.31388330587589236, 0.7290645927383982), 187 | (0.31388330587589236, 0.03637394673723693), 188 | (0.31461641954969793, 0.4043260962749117), 189 | (0.3153495332235035, 0.13560529824092837), 190 | (0.31791543108182313, 0.6012521314458483), 191 | (0.321947556287754, 0.7305419825870885), 192 | (0.321947556287754, 0.03789237519283528), 193 | (0.3226806699615596, 0.40206897289496835), 194 | (0.32341378363536516, 0.13954500450410223), 195 | (0.33001180669961555, 0.7318141794012383), 196 | (0.33001180669961555, 0.03965703528988174), 197 | (0.3307449203734211, 0.3996887336943008), 198 | (0.3314780340472268, 0.14352574937418416), 199 | (0.3380760571114772, 0.732922221787756), 200 | (0.3380760571114772, 0.041421695386928414), 201 | (0.3388091707852828, 0.39718537867290904), 202 | (0.33954228445908835, 0.14787584170643864), 203 | (0.34614030752333885, 0.7339481869604575), 204 | (0.34614030752333885, 0.04322739409088305), 205 | (0.3468734211971444, 0.3943947534031609), 206 | (0.34760653487095, 0.15234904985941722), 207 | (0.3542045579352004, 0.7346458432778945), 208 | (0.3542045579352004, 0.045197247222470094), 209 | (0.35493767160900597, 0.3913578964919644), 210 | (0.35567078528281165, 0.15715056686766038), 211 | (0.36226880834706204, 0.735015190740067), 212 | (0.36226880834706204, 0.04716710035405702), 213 | (0.3630019220208676, 0.3880748079393195), 214 | (0.3637350356946732, 0.1621572769104438), 215 | (0.3703330587589237, 0.7357538856644121), 216 | (0.3703330587589237, 0.049177992092552025), 217 | (0.37106617243272927, 0.38450444913831827), 218 | (0.37179928610653484, 0.167615411629216), 219 | (0.37839730917078523, 0.7358770014851364), 220 | (0.37839730917078523, 0.05135303825867921), 221 | (0.3791304228445908, 0.38052370426823634), 222 | (0.3798635365183965, 0.17331977798943643), 223 | (0.3864615595826469, 0.7358770014851364), 224 | (0.3864615595826469, 0.0535280844248065), 225 | (0.38719467325645246, 0.37617361193598187), 226 | (0.38792778693025803, 0.17947556902564565), 227 | (0.39452580999450854, 0.7357949242713202), 228 | (0.39452580999450854, 0.055949362232382116), 229 | (0.3952589236683141, 0.3714952107484629), 230 | (0.3959920373421197, 0.18612382334475153), 231 | (0.4025900604063701, 0.7350562293469751), 232 | (0.4025900604063701, 0.05816544700541726), 233 | (0.40332317408017565, 0.3660370760296908), 234 | (0.40405628775398134, 0.1934697339812944), 235 | (0.4106543108182317, 0.7347279204917107), 236 | (0.4106543108182317, 0.06062776341990095), 237 | (0.4113874244920373, 0.36008647802802196), 238 | (0.4121205381658429, 0.2013901851145502), 239 | (0.4187185612300934, 0.7339892255673656), 240 | (0.4187185612300934, 0.06325423426201693), 241 | (0.41945167490389895, 0.3532330306743757), 242 | (0.4201847885777045, 0.2105828330619558), 243 | (0.4267828116419549, 0.7330453376084801), 244 | (0.4267828116419549, 0.06588070510413291), 245 | (0.4275159253157605, 0.34518946372039583), 246 | (0.4275159253157605, 0.21994875512741774), 247 | (0.43484706205381657, 0.7318962566150544), 248 | (0.4337473915431082, 0.22932208694134915), 249 | (0.43484706205381657, 0.06863029176697288), 250 | (0.43521361889071936, 0.3360050234943718), 251 | (0.4396123009335529, 0.23936628598209708), 252 | (0.4429113124656782, 0.7306650984078127), 253 | (0.44181164195496975, 0.326434820363412), 254 | (0.4429113124656782, 0.071461955643629), 255 | (0.44547721032399773, 0.25014405012132657), 256 | (0.446943437671609, 0.31665395238365734), 257 | (0.45097556287753976, 0.7291466699522143), 258 | (0.45097556287753976, 0.07433465812719331), 259 | (0.450609006040637, 0.2602258678850735), 260 | (0.45097556287753976, 0.30584985513832863), 261 | (0.45390801757276217, 0.293480819016239), 262 | (0.45427457440966496, 0.27124815372379696), 263 | (0.45500768808347064, 0.2812923527645449), 264 | (0.4590398132894014, 0.727505125675892), 265 | (0.4590398132894014, 0.07733047643148172), 266 | (0.46710406370126306, 0.7256173497581211), 267 | (0.46710406370126306, 0.08040837194958639), 268 | (0.4751683141131246, 0.723483342198902), 269 | (0.4751683141131246, 0.08360938328841516), 270 | (0.48323256452498625, 0.721472450460407), 271 | (0.48323256452498625, 0.08660520159270357), 272 | (0.4912968149368479, 0.7190101340459233), 273 | (0.4912968149368479, 0.0900114059660726), 274 | (0.49936106534870944, 0.7162605473830832), 275 | (0.49936106534870944, 0.09345864894634981), 276 | (0.507425315760571, 0.713428883506427), 277 | (0.507425315760571, 0.0970700463542592), 278 | (0.5154895661724328, 0.7102689107745063), 279 | (0.5154895661724328, 0.1008045595828927), 280 | (0.5235538165842943, 0.7069447836149534), 281 | (0.5235538165842943, 0.10466218863225041), 282 | (0.5211101043382758, 0.779364245272045), 283 | (0.5206213618890718, 0.7679281534803319), 284 | (0.5211101043382758, 0.7552882625526491), 285 | (0.5228207029104888, 0.7930574604437013), 286 | (0.5228207029104888, 0.7426483716249663), 287 | (0.5253866007688084, 0.804944976911403), 288 | (0.5253866007688084, 0.7306856177112666), 289 | (0.5286856123009336, 0.8158694540703288), 290 | (0.5286856123009336, 0.719715998084742), 291 | (0.5335616736366324, 0.7039198763245393), 292 | (0.5316180669961559, 0.10851981768160812), 293 | (0.5327177375068644, 0.8260866992368724), 294 | (0.5374829763866007, 0.8357708333574865), 295 | (0.5396823174080176, 0.1127057555862303), 296 | (0.5433478857770455, 0.8455731977503833), 297 | (0.5477465678198792, 0.11685065488394453), 298 | (0.5506790225151015, 0.8555045406221341), 299 | (0.5558108182317407, 0.121200747216199), 300 | (0.5587432729269632, 0.8646561499626317), 301 | (0.5638750686436025, 0.12575603258299384), 302 | (0.5668075233388248, 0.8721662150268068), 303 | (0.571939319055464, 0.13031131794978856), 304 | (0.5748717737506863, 0.8784040832768322), 305 | (0.5800035694673256, 0.13505127704766962), 306 | (0.5829360241625481, 0.8836159863541558), 307 | (0.5880678198791871, 0.1395860431110103), 308 | (0.5910002745744096, 0.8879660786864103), 309 | (0.5961320702910489, 0.14459275315379383), 310 | (0.5990645249862712, 0.8914953988805036), 311 | (0.6041963207029104, 0.15019452299674407), 312 | (0.6071287753981327, 0.8941218697226194), 313 | (0.612260571114772, 0.15587837005351057), 314 | (0.6151930258099945, 0.8961738000680225), 315 | (0.6203248215266337, 0.16133650477228267), 316 | (0.623257276221856, 0.8976922285236208), 317 | (0.6223775398132894, 0.8015141493738891), 318 | (0.62509006040637, 0.7857594281818845), 319 | (0.6261897309170785, 0.8173914001208049), 320 | (0.6283890719384952, 0.16699983252559503), 321 | (0.6313215266337175, 0.8983898848410579), 322 | (0.6313215266337175, 0.7763000292895764), 323 | (0.6327877539813289, 0.8259157050414221), 324 | (0.6364533223503568, 0.1729504305272639), 325 | (0.6393857770455793, 0.8983898848410579), 326 | (0.6393857770455793, 0.7704315085017237), 327 | (0.6408520043931905, 0.8309634536911137), 328 | (0.6445175727622185, 0.17918829877728915), 329 | (0.6474500274574408, 0.8981026145927014), 330 | (0.6474500274574408, 0.7683795781563206), 331 | (0.648916254805052, 0.8323177277190796), 332 | (0.6525818231740801, 0.18575447588257887), 333 | (0.6555142778693024, 0.8970356108130918), 334 | (0.6555142778693024, 0.769980083825735), 335 | (0.6569805052169138, 0.8301426815529525), 336 | (0.6606460735859416, 0.19236169159477678), 337 | (0.6635785282811641, 0.8952709507160452), 338 | (0.6635785282811641, 0.7752330255099669), 339 | (0.6650447556287753, 0.8238637746960191), 340 | (0.6687103239978034, 0.19929721616223894), 341 | (0.6716427786930257, 0.8928907115153777), 342 | (0.6698099945085116, 0.7837280171399355), 343 | (0.6709096650192201, 0.8146957499127583), 344 | (0.6731090060406368, 0.7932079353356976), 345 | (0.6738421197144424, 0.8034402279914408), 346 | (0.6767745744096649, 0.2065610495849659), 347 | (0.6797070291048872, 0.8897717773903651), 348 | (0.6848388248215265, 0.21411215325604904), 349 | (0.687771279516749, 0.8858731097340992), 350 | (0.6929030752333882, 0.22203260438930483), 351 | (0.6958355299286105, 0.8811536699396723), 352 | (0.7009673256452498, 0.2302813643778251), 353 | (0.7038997803404721, 0.8754082649725436), 354 | (0.7090315760571113, 0.239022587649242), 355 | (0.7119640307523338, 0.8685137790119895), 356 | (0.7170958264689731, 0.24805108116901542), 357 | (0.7200282811641954, 0.8602650190234692), 358 | (0.7251600768808346, 0.25781826961313403), 359 | (0.7254258649093901, 0.8542731364906561), 360 | (0.7328577704557935, 0.2675690426144892), 361 | (0.7361567819879187, 0.8550531159461454), 362 | (0.7401889071938494, 0.2775455279538389), 363 | (0.7442210323997802, 0.8550531159461454), 364 | (0.7411419549697966, 0.6929013723310149), 365 | (0.7423882482152662, 0.6765146565926261), 366 | (0.7434879187259746, 0.7089419958178123), 367 | (0.7460538165842943, 0.6653795145849056), 368 | (0.7471534870950027, 0.28751198163372216), 369 | (0.7460538165842943, 0.7191742884735555), 370 | (0.7489862712795166, 0.7293313436833007), 371 | (0.7522852828116418, 0.8546837684839729), 372 | (0.7511856123009335, 0.6553541249056571), 373 | (0.7544846238330587, 0.7419917539144374), 374 | (0.7533849533223502, 0.2970922164241484), 375 | (0.7570505216913783, 0.6457613505408979), 376 | (0.7603495332235035, 0.8536578033112714), 377 | (0.759249862712795, 0.3066285627044091), 378 | (0.7625488742449202, 0.749830127833877), 379 | (0.7629154310818231, 0.6357171515001501), 380 | (0.7651147721032399, 0.3169549021676499), 381 | (0.768413783635365, 0.8520162590349489), 382 | (0.768413783635365, 0.6256004020650467), 383 | (0.7706131246567818, 0.7584071966776618), 384 | (0.7706131246567818, 0.3272731804759623), 385 | (0.7735455793520043, 0.6156690591932961), 386 | (0.7764780340472266, 0.8498822514757297), 387 | (0.775744920373421, 0.3377204372631287), 388 | (0.7783108182317406, 0.7677927260775353), 389 | (0.7783108182317406, 0.6054152701244104), 390 | (0.7805101592531576, 0.34825367970286436), 391 | (0.7845422844590884, 0.8472968192405219), 392 | (0.7827095002745743, 0.5953334523606635), 393 | (0.7852753981328939, 0.7775585465680903), 394 | (0.784908841295991, 0.35893739703459626), 395 | (0.7867416254805051, 0.5849205898345249), 396 | (0.7889409665019218, 0.3699521591287198), 397 | (0.7926065348709499, 0.844013730687877), 398 | (0.7915068643602414, 0.7875651268858391), 399 | (0.7904071938495332, 0.5746281072219831), 400 | (0.792239978034047, 0.37959007596107797), 401 | (0.7937062053816584, 0.5643356246094415), 402 | (0.7951724327292695, 0.38952141883282865), 403 | (0.7970052169137836, 0.7975932036167304), 404 | (0.7966386600768807, 0.5538400008927049), 405 | (0.8006707852828114, 0.8401971402454274), 406 | (0.7981048874244918, 0.4005813233945511), 407 | (0.7992045579352003, 0.5434196146219663), 408 | (0.8017704557935199, 0.8075030500753388), 409 | (0.8006707852828114, 0.41205503390926324), 410 | (0.801403898956617, 0.5327358972902345), 411 | (0.8028701263042284, 0.424093025268961), 412 | (0.8032366831411311, 0.5221274174045007), 413 | (0.8058025809994507, 0.8170431582278994), 414 | (0.8045807248764414, 0.511518937518767), 415 | (0.804922844590884, 0.4369435810454385), 416 | (0.8117018121911039, 0.833955606187257), 417 | (0.8058025809994507, 0.49955618360506715), 418 | (0.8058690280065897, 0.4511296437566108), 419 | (0.8069022515101592, 0.4857877309874128), 420 | (0.8069022515101592, 0.475856388115662), 421 | (0.8069022515101592, 0.4659250452439112), 422 | (0.8087350356946732, 0.8243562379789158), 423 | ]; 424 | 425 | plot.plotopt( 426 | ys, 427 | vec![ 428 | Opt::Size(0.75), 429 | Opt::Color(colors.get(&PlotColor::Black).unwrap()[4]), 430 | Opt::LineStyle(LineStyle::None), 431 | Opt::Mech(false), 432 | Opt::MarkerStyle(MarkerStyle::Circle), 433 | Opt::MarkerSize(0.5), 434 | Opt::Contour(false), 435 | Opt::MarkerColor(colors.get(&PlotColor::Green).unwrap()[5]), 436 | Opt::MarkerInnerPointColor(colors.get(&PlotColor::Green).unwrap()[5]), 437 | ], 438 | ); 439 | 440 | let plot_handle = plots.add(plot.clone()); 441 | commands.spawn().insert(plot_handle); 442 | } 443 | -------------------------------------------------------------------------------- /examples/bevy0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/examples/bevy0.png -------------------------------------------------------------------------------- /examples/func.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_plot::*; 3 | 4 | fn main() { 5 | App::new() 6 | // .insert_resource(WindowDescriptor { 7 | // width: 800., 8 | // height: 600., 9 | // ..Default::default() 10 | // }) 11 | .add_plugins(( 12 | PlotPlugin, 13 | DefaultPlugins.set(WindowPlugin { 14 | primary_window: Some(Window { 15 | title: "I am a window!".into(), 16 | name: Some("bevy.app".into()), 17 | resolution: (800., 600.).into(), 18 | 19 | ..default() 20 | }), 21 | ..default() 22 | }), 23 | )) 24 | .add_systems(Startup, setup) 25 | .run(); 26 | } 27 | 28 | fn setup( 29 | mut commands: Commands, 30 | mut plots: ResMut, 31 | // asset_server: Res, 32 | // mut maybe_font: ResMut, 33 | ) { 34 | commands.spawn(Camera2d::default()); 35 | 36 | // let font: Handle = asset_server.load("fonts/Roboto-Bold.ttf"); 37 | // maybe_font.maybe_font = Some(font); 38 | 39 | let mut plot = Plot::default(); 40 | plot.canvas_size = Vec2::new(790.0, 590.0); 41 | 42 | plot.show_target = true; 43 | plot.show_grid = true; 44 | plot.hide_contour = true; 45 | // plot.hide_tick_labels = true; 46 | 47 | // // transparent background 48 | // plot.background_color1 = Color::rgba(0.0, 0.0, 0.0, 0.0); 49 | // plot.background_color2 = Color::rgba(0.0, 0.0, 0.0, 0.0); 50 | 51 | // extremeties of the graph axes 52 | let lower_bound = Vec2::new(-1.5, -1.0); 53 | let upper_bound = Vec2::new(3.0, 10.0); 54 | plot.set_bounds(lower_bound, upper_bound); 55 | 56 | // note that a closure would work as well 57 | plot.plot_func(easing_func); 58 | 59 | plots.add(plot.clone()); 60 | } 61 | 62 | // The function is not animated, so we don't use the time variable t. 63 | pub fn easing_func(x: f32, _t: f32) -> f32 { 64 | let start_point: Vec2 = Vec2::ZERO; 65 | let end_point: Vec2 = Vec2::splat(1.0); 66 | let y_min = start_point.y; 67 | let y_max = end_point.y; 68 | 69 | // visual bug appears when the exponent is close to zero 70 | let expo: f32 = 7.1; 71 | 72 | let xp = (x - start_point.x) / (end_point.x - start_point.x); 73 | let mut sign = (1.0 - xp).signum(); 74 | if sign == 0.0 { 75 | sign = 1.0; 76 | } 77 | let f = y_max - sign * (1.0 - xp).abs().powf(expo) * (y_max - y_min); 78 | 79 | return f; 80 | } 81 | -------------------------------------------------------------------------------- /examples/markers.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use bevy_plot::*; 4 | 5 | use itertools_num::linspace; 6 | use std::collections::HashMap; 7 | 8 | fn main() { 9 | App::new() 10 | .add_plugins(DefaultPlugins) 11 | .add_plugin(PlotPlugin) 12 | .add_startup_system(setup) 13 | .run(); 14 | } 15 | 16 | fn setup( 17 | mut commands: Commands, 18 | colors_res: Res>>, 19 | mut plots: ResMut>, 20 | asset_server: Res, 21 | mut maybe_font: ResMut, 22 | ) { 23 | commands.spawn_bundle(Camera2dBundle::default()); 24 | let font: Handle = asset_server.load("fonts/Roboto-Bold.ttf"); 25 | maybe_font.maybe_font = Some(font); 26 | 27 | let colors = colors_res.as_ref(); 28 | let mut plot = Plot::default(); 29 | 30 | let xs_linspace = linspace(-0.1, 1.1, 32); 31 | let xs = xs_linspace.into_iter().collect::>(); 32 | 33 | let ys = xs 34 | .iter() 35 | .map(|x| Vec2::new(*x, f(*x))) 36 | .collect::>(); 37 | 38 | plot.plotopt( 39 | ys, 40 | vec![ 41 | Opt::LineStyle(LineStyle::None), 42 | Opt::Mech(false), 43 | Opt::MarkerStyle(MarkerStyle::Triangle), 44 | Opt::MarkerSize(2.0), 45 | Opt::Contour(true), 46 | Opt::MarkerColor(colors.get(&PlotColor::Green).unwrap()[5]), 47 | Opt::MarkerInnerPointColor(Color::BLACK), 48 | ], 49 | ); 50 | 51 | let plot_handle = plots.add(plot.clone()); 52 | commands.spawn().insert(plot_handle); 53 | } 54 | 55 | pub fn f(mut x: f32) -> f32 { 56 | let freq = 15.0; 57 | x = x - 0.5; 58 | let y = (x * freq).sin() / 4.0 * (1.2 - x.abs()) + 0.3; 59 | return y; 60 | } 61 | -------------------------------------------------------------------------------- /examples/minimal.rs: -------------------------------------------------------------------------------- 1 | use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; 2 | use bevy::prelude::*; 3 | use bevy::window::{PresentMode, WindowTheme}; 4 | use bevy_plot::*; 5 | 6 | use bevy_framepace::FramepacePlugin; 7 | 8 | fn main() { 9 | App::new() 10 | .add_plugins(( 11 | DefaultPlugins.set(WindowPlugin { 12 | primary_window: Some(Window { 13 | title: "I am a window!".into(), 14 | name: Some("bevy.app".into()), 15 | resolution: (1000., 600.).into(), 16 | present_mode: PresentMode::AutoVsync, 17 | // Tells Wasm to resize the window according to the available canvas 18 | fit_canvas_to_parent: true, 19 | // Tells Wasm not to override default event handling, like F5, Ctrl+R etc. 20 | prevent_default_event_handling: false, 21 | window_theme: Some(WindowTheme::Dark), 22 | enabled_buttons: bevy::window::EnabledButtons { 23 | maximize: false, 24 | ..Default::default() 25 | }, 26 | // This will spawn an invisible window 27 | // The window will be made visible in the make_visible() system after 3 frames. 28 | // This is useful when you want to avoid the white window that shows up before the GPU is ready to render the app. 29 | visible: true, 30 | ..default() 31 | }), 32 | ..default() 33 | }), 34 | // LogDiagnosticsPlugin::default(), 35 | // FrameTimeDiagnosticsPlugin::default(), 36 | PlotPlugin, 37 | )) 38 | .add_systems(Startup, setup) 39 | // .add_systems(Update, frame_limiter) 40 | .add_plugins(FramepacePlugin) 41 | .run(); 42 | } 43 | 44 | // If no fonts are loaded and put into the TickLabelFont resource, 45 | // the canvas will not include the tick labels. See the "markers" example 46 | // for an instance of loading a font. 47 | fn setup( 48 | mut commands: Commands, 49 | mut plots: ResMut, 50 | asset_server: Res, 51 | mut settings: ResMut, 52 | ) { 53 | let font_handle = TickLabelFont { 54 | maybe_font: Some(asset_server.load("fonts/FiraSans-Bold.ttf")), 55 | }; 56 | commands.insert_resource(font_handle); 57 | 58 | commands.spawn(Camera2d::default()); 59 | 60 | let mut plot = Plot::default(); 61 | 62 | let xs = (0..30).map(|i| i as f32 / 30.0).collect::>(); 63 | 64 | let ys = xs.iter().map(|x| Vec2::new(*x, 0.5 * x)).collect::>(); 65 | 66 | plot.plot(ys); 67 | plot.set_bounds(Vec2::new(-1.0, -1.0), Vec2::new(1.0, 0.0)); 68 | 69 | plots.add(plot.clone()); 70 | 71 | settings.limiter = bevy_framepace::Limiter::from_framerate(60.0); 72 | // commands.spawn().insert(plot_handle); 73 | } 74 | use std::thread; 75 | use std::thread::sleep; 76 | use std::time::{Duration, Instant}; 77 | 78 | fn frame_limiter() { 79 | static mut LAST_FRAME_TIME: Option = None; 80 | let target_frame_duration = Duration::from_secs_f64(1.0 / 60.0); // Cap to 60 FPS 81 | 82 | unsafe { 83 | let now = Instant::now(); 84 | if let Some(last_frame_time) = LAST_FRAME_TIME { 85 | let frame_time = now.duration_since(last_frame_time); 86 | 87 | if frame_time < target_frame_duration { 88 | sleep(target_frame_duration - frame_time); 89 | } 90 | } 91 | LAST_FRAME_TIME = Some(Instant::now()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/runtime_setter.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_plot::*; 3 | 4 | use std::collections::HashMap; 5 | 6 | fn main() { 7 | App::new() 8 | .insert_resource(WindowDescriptor { 9 | width: 1000., 10 | height: 800., 11 | ..Default::default() 12 | }) 13 | .add_plugins(DefaultPlugins) 14 | .add_plugin(PlotPlugin) 15 | .add_startup_system(setup) 16 | .add_system(change_bezier_metaparameters_at_runtime) 17 | .run(); 18 | } 19 | 20 | use bevy_plot::UpdateBezierShaderEvent; 21 | 22 | // Press Mouse::Right and drag the mouse to change the thickness of the curve 23 | pub fn change_bezier_metaparameters_at_runtime( 24 | mut plots: ResMut>, 25 | query: Query<(Entity, &Handle, &BezierCurveNumber)>, 26 | mut cursor_moved_events: EventReader, 27 | mouse_button_input: Res>, 28 | mut event: EventWriter, 29 | ) { 30 | for mouse_motion_event in cursor_moved_events.iter() { 31 | for (entity, plot_handle, curve_number) in query.iter() { 32 | let plot = plots.get_mut(plot_handle).unwrap(); 33 | 34 | if mouse_button_input.pressed(MouseButton::Right) { 35 | if let Some(mut bezier_data) = plot.data.bezier_groups.get_mut(curve_number.0) { 36 | bezier_data.size = mouse_motion_event.position.x / 100.0; 37 | 38 | // If show_animation is set to true, UpdateBezierShaderEvent will be sent elsewhere anyway, 39 | // so we don't need to send it twice every frame. 40 | if !bezier_data.show_animation { 41 | event.send(UpdateBezierShaderEvent { 42 | plot_id: plot_handle.clone(), 43 | entity, 44 | group_number: curve_number.0, 45 | }); 46 | } 47 | 48 | // For updating a scatter plot (markers) or a regular plot (segments), send 49 | // the RespawnAllEvent event. This will despawn all the entities and respawn 50 | // them with the updated information. 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | fn setup( 58 | mut commands: Commands, 59 | colors_res: Res>>, 60 | mut plots: ResMut>, 61 | asset_server: Res, 62 | mut maybe_font: ResMut, 63 | ) { 64 | commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 65 | let font: Handle = asset_server.load("fonts/Roboto-Bold.ttf"); 66 | maybe_font.maybe_font = Some(font); 67 | 68 | let colors = colors_res.as_ref(); 69 | 70 | let mut plot = Plot::default(); 71 | 72 | // sine wave 73 | plot.plotopt_func( 74 | f3, 75 | vec![ 76 | Opt::Size(2.0), 77 | Opt::Color(colors.get(&PlotColor::LightPink).unwrap()[1]), 78 | ], 79 | ); 80 | 81 | let plot_handle = plots.add(plot.clone()); 82 | commands.spawn().insert(plot_handle); 83 | } 84 | 85 | pub fn f3(x: f32, t: f32) -> f32 { 86 | let freq = 20.0; 87 | let y = (x * freq + t * 0.0).sin() / 2.0 + 0.5; 88 | return y; 89 | } 90 | -------------------------------------------------------------------------------- /src/bezier/bezier_spline.wgsl: -------------------------------------------------------------------------------- 1 | // #import bevy_sprite::mesh2d_view_bind_group 2 | // #import bevy_sprite::mesh2d_types 3 | // #import bevy_sprite::mesh2d_view_types 4 | 5 | #import bevy_sprite::mesh2d_bindings 6 | #import bevy_sprite::mesh2d_view_bindings 7 | 8 | 9 | 10 | 11 | struct BezierCurveUniform { 12 | mech: f32, 13 | zoom: f32, 14 | inner_canvas_size_in_pixels: vec2, 15 | canvas_position_in_pixels: vec2, 16 | color: vec4, 17 | size: f32, 18 | dummy: f32, 19 | style: i32, 20 | }; 21 | 22 | // @group(0) @binding(0) 23 | // var view: View; 24 | 25 | @group(1) @binding(0) 26 | var bez_uni: BezierCurveUniform; 27 | 28 | 29 | 30 | // // #import bevy_sprite::mesh2d_struct 31 | // @group(2) @binding(0) 32 | // var mesh: Mesh2d; 33 | 34 | // The structure of the vertex buffer is as specified in `specialize()` 35 | struct Vertex { 36 | @location(0) position: vec3, 37 | @location(1) ends: vec4, 38 | @location(2) uv: vec2, 39 | @location(3) control: vec4, 40 | }; 41 | struct VertexOutput { 42 | @builtin(position) clip_position: vec4, 43 | @location(0) ends: vec4, 44 | @location(1) uv: vec2, 45 | @location(2) control: vec4, 46 | }; 47 | /// Entry point for the vertex shader 48 | @vertex 49 | fn vertex(vertex: Vertex) -> VertexOutput { 50 | var out: VertexOutput; 51 | // Project the world position of the mesh into screen position 52 | out.clip_position = view.view_proj * mesh.model * vec4(vertex.position, 1.0); 53 | out.ends = vertex.ends; 54 | out.uv = vertex.uv; 55 | out.control = vertex.control; 56 | return out; 57 | } 58 | 59 | fn fromLinear(linearRGB: vec4) -> vec4 { 60 | let cutoff: vec4 = vec4(linearRGB < vec4(0.0031308)); 61 | let higher: vec4 = vec4(1.055) * pow(linearRGB, vec4(1.0 / 2.4)) - vec4(0.055); 62 | let lower: vec4 = linearRGB * vec4(12.92); 63 | 64 | return mix(higher, lower, cutoff); 65 | } 66 | 67 | // Converts a color from sRGB gamma to linear light gamma (unused?) 68 | fn toLinear(sRGB: vec4) -> vec4 { 69 | let cutoff = vec4(sRGB < vec4(0.04045)); 70 | let higher = pow((sRGB + vec4(0.055)) / vec4(1.055), vec4(2.4)); 71 | let lower = sRGB / vec4(12.92); 72 | 73 | return mix(higher, lower, cutoff); 74 | } 75 | 76 | 77 | struct FragmentInput { 78 | @location(0) ends: vec4, 79 | @location(1) uv: vec2, 80 | @location(2) control: vec4, 81 | }; 82 | 83 | 84 | 85 | 86 | 87 | fn sdBezier(p: vec2, A: vec2, B: vec2, C: vec2) -> vec2 { 88 | let a = B - A; 89 | let b = A - 2. * B + C; 90 | let c = a * 2.; 91 | let d = A - p; 92 | let kk = 1. / dot(b, b); 93 | let kx = kk * dot(a, b); 94 | let ky = kk * (2. * dot(a, a) + dot(d, b)) / 3.; 95 | let kz = kk * dot(d, a); 96 | 97 | let p1 = ky - kx * kx; 98 | let p3 = p1 * p1 * p1; 99 | let q = kx * (2.0 * kx * kx - 3.0 * ky) + kz; 100 | var h: f32 = q * q + 4. * p3; 101 | 102 | var res: vec2; 103 | if h >= 0. { 104 | h = sqrt(h); 105 | let x = (vec2(h, -h) - q) / 2.; 106 | let uv = sign(x) * pow(abs(x), vec2(1. / 3.)); 107 | let t = clamp(uv.x + uv.y - kx, 0., 1.); 108 | let f = d + (c + b * t) * t; 109 | res = vec2(dot(f, f), t); 110 | } else { 111 | let z = sqrt(-p1); 112 | let v = acos(q / (p1 * z * 2.)) / 3.; 113 | let m = cos(v); 114 | let n = sin(v) * 1.732050808; 115 | let t = clamp(vec2(m + m, -n - m) * z - kx, vec2(0.0), vec2(1.0)); 116 | let f = d + (c + b * t.x) * t.x; 117 | var dis: f32 = dot(f, f); 118 | res = vec2(dis, t.x); 119 | 120 | let g = d + (c + b * t.y) * t.y; 121 | dis = dot(g, g); 122 | res = select(res, vec2(dis, t.y), dis < res.x); 123 | } 124 | res.x = sqrt(res.x); 125 | return res; 126 | } 127 | 128 | fn dot2(v: vec2) -> f32 { return dot(v, v); } 129 | fn cro(a: vec2, b: vec2) -> f32 { return a.x * b.y - a.y * b.x; } 130 | 131 | fn sdBezier2(p: vec2, v0q: vec2, v1q: vec2, v2q: vec2) -> vec2 { 132 | let i = v0q - v2q; 133 | let j = v2q - v1q; 134 | let k = v1q - v0q; 135 | let w = j - k; 136 | 137 | var v0 = v0q; 138 | var v1 = v1q; 139 | var v2 = v2q; 140 | 141 | v0 = v0 - p; 142 | v1 = v1 - p; 143 | v2 = v2 - p; 144 | 145 | let x = cro(v0, v2); 146 | let y = cro(v1, v0); 147 | let z = cro(v2, v1); 148 | 149 | let s = 2.0 * (y * j + z * k) - x * i; 150 | 151 | let r = (y * z - x * x * 0.25) / dot2(s); 152 | let t = clamp((0.5 * x + y + r * dot(s, w)) / (x + y + z), 0.0, 1.0); 153 | 154 | return vec2(length(v0 + t * (k + k + t * w)), 0.0) ; 155 | } 156 | 157 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 158 | let pa = p - a; 159 | let ba = b - a; 160 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.); 161 | return length(pa - ba * h); 162 | } 163 | 164 | fn sdCircle(p: vec2, c: vec2, r: f32) -> f32 { 165 | let d = length(p - c); 166 | return d - r; 167 | } 168 | 169 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 170 | var x = r.x; 171 | var y = r.y; 172 | x = select(r.z, r.x, p.x > 0.); 173 | y = select(r.w, r.y, p.x > 0.); 174 | x = select(y, x, p.y > 0.); 175 | let q = abs(p) - b + x; 176 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 177 | } 178 | 179 | fn sdPie(p: vec2, sc: vec2, r: f32) -> f32 { 180 | let q = vec2(abs(p.x), p.y); 181 | let l = length(q) - r; 182 | let m = length(q - sc * clamp(dot(q, sc), 0., r)); 183 | return max(l, m * sign(sc.y * q.x - sc.x * q.y)); 184 | } 185 | 186 | fn tips(uv: vec2, m_in: vec4, dy: vec2, solid: f32, w: f32) -> vec4 { 187 | var m = m_in; 188 | let theta = atan2(dy.y, dy.x) + 3.1415 / 2.0; 189 | let ma = mat2x2(vec2(cos(theta), -sin(theta)), vec2(sin(theta), cos(theta))); 190 | let uvrot = ma * uv ; 191 | let angle = 3.1415 / 2.0; 192 | let pies = sdPie(uvrot, vec2(sin(angle), cos(angle)), solid / 0.9); 193 | let sp = smoothstep(0.0, w, pies); 194 | // let mp = mix(m, vec4(0.0, 1.0, 0.0, 1.0), 1.0-sp); 195 | m.a = m.a * (sp); 196 | return m; 197 | } 198 | 199 | @fragment 200 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 201 | let width = bez_uni.size / 1.0; 202 | // let w = 1.0 + width * bez_uni.zoom * 1.0; 203 | // let solid = width * bez_uni.zoom ; 204 | let w = 1.0 + width * 1.0; 205 | let solid = width ; 206 | 207 | 208 | 209 | // var out_col = vec4(0.2, 0.3, 0.8, 1.00); 210 | var out_col = bez_uni.color; 211 | 212 | var uv = in.uv; 213 | 214 | var p0 = in.ends.xy; 215 | var p1 = in.ends.zw; 216 | 217 | var control = in.control.xy; 218 | let is_last = in.control.w; 219 | 220 | control.x = clamp(p0.x, p1.x, control.x); 221 | 222 | if control.x < min(p1.x, p0.x) { 223 | control.x = min(p1.x, p0.x); 224 | } 225 | 226 | if control.x > max(p1.x, p0.x) { 227 | control.x = max(p1.x, p0.x); 228 | } 229 | 230 | 231 | let d = sdBezier(uv, p0, control, p1); 232 | let s = smoothstep(0.0 + solid, w + solid, d.x); 233 | 234 | // mechanical look 235 | if bez_uni.mech > 0.5 { 236 | // if (false) { 237 | let c0 = sdCircle(in.uv, p0, w); 238 | let sc0 = smoothstep(0.0 + solid, w + solid, c0); 239 | 240 | let solid_c = solid / 3.0; 241 | let w_c = w / 2.0; 242 | 243 | let c1 = sdCircle(in.uv, p1, 0.2); 244 | let sc1 = smoothstep(0.0 + solid_c, (w_c + solid_c), abs(c1)); 245 | 246 | out_col.a = out_col.a * (1.0 - s) * (sc1) * sc0; 247 | } else { 248 | // smooth look 249 | out_col.a = out_col.a * (1.0 - s); 250 | 251 | // correcting for artifacts at the intersection of two bezier end-points 252 | // by displacing a circle in the direction of the derivative. 253 | // Thw artifact variable was chosen experimentally for sizes of 0.5, 1.0 and 2.0; 254 | 255 | if is_last < 0.5 { 256 | var artifact = 1.0; 257 | if bez_uni.size > 1.9 { 258 | artifact = 0.78; 259 | } 260 | if bez_uni.size < 0.6 { 261 | artifact = 1.75; 262 | } 263 | let dy = normalize(p1 - control); 264 | let dc = sdCircle(in.uv, p1 + dy * width * 2.12 * artifact, w); 265 | // let dc = sdCircle(in.uv, p1 + dy * width * bez_uni.dummy * 2.12 * artifact , w); 266 | 267 | let sc = smoothstep(solid, w * 0.9 * 0.5 + solid, dc); 268 | 269 | // let dy = normalize(p1 - control); 270 | // let dc = sdCircle(in.uv, p1 + dy * 4.0 , 0.0); 271 | // let sc = smoothstep(solid, solid + w * 1.0 , dc); 272 | 273 | 274 | out_col.a = out_col.a - (1.0 - sc) ; 275 | 276 | let div = 5.0; 277 | let dy1 = normalize(p1 - control); 278 | let dy0 = normalize(p0 - control); 279 | let mu = 0.6; 280 | 281 | // out_col = tips(in.uv- p0 - dy0 * mu, out_col, control - p0, solid*2.5, w / div ); 282 | // out_col = tips(in.uv- p1 - dy1 * mu, out_col, control - p1, solid*2.5, w / div ); 283 | } 284 | } 285 | 286 | // let dc = sdCircle(in.uv, p1 + vec2(0.0, 6.0) , 1.2); 287 | // let sc = smoothstep(solid, solid + w , dc); 288 | // out_col = mix(out_col, vec4(0.5, 0.0, 0.0, 1.0), 1.0 - sc); 289 | 290 | // let dc = sdCircle(in.uv, control.xy , 1.2); 291 | // let sc = smoothstep(solid, solid + w , dc); 292 | // out_col = mix(out_col, vec4(0.5, 0.0, 0.5, 1.0), 1.0 - sc); 293 | 294 | 295 | // mask with the canvas 296 | let r = 0.02 * bez_uni.inner_canvas_size_in_pixels.x; 297 | let d = sdRoundedBox( 298 | // in.uv - bez_uni.canvas_position_in_pixels , 299 | in.uv, 300 | bez_uni.inner_canvas_size_in_pixels / 2.0 - 1.0, 301 | vec4(r, r, r, r) 302 | ); 303 | 304 | let s = smoothstep(-2.0, 0.0, d); 305 | out_col = mix(out_col, vec4(0.0, 0.3, 0.3, 0.0), s) ; 306 | 307 | // out_col = vec4(0.0,0.3,0.3,1.0); 308 | 309 | 310 | return out_col; 311 | } -------------------------------------------------------------------------------- /src/bezier/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bezier; 2 | #[allow(unused_imports)] 3 | pub use bezier::*; 4 | -------------------------------------------------------------------------------- /src/bezier2/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod segments; 2 | 3 | pub use segments::*; 4 | -------------------------------------------------------------------------------- /src/bezier2/segments.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | core::FloatOrd, 3 | core_pipeline::Transparent2d, 4 | ecs::system::lifetimeless::{Read, SQuery, SRes}, 5 | ecs::system::SystemParamItem, 6 | prelude::*, 7 | // reflect::TypeUuid, 8 | render::{ 9 | mesh::GpuBufferInfo, 10 | mesh::Indices, 11 | render_asset::RenderAssets, 12 | render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, 13 | render_component::{ExtractComponent, ExtractComponentPlugin}, 14 | render_phase::{ 15 | AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, 16 | SetItemPipeline, TrackedRenderPass, 17 | }, 18 | render_resource::{std140::AsStd140, *}, 19 | renderer::RenderDevice, 20 | // texture::BevyDefault, 21 | // texture::GpuImage, 22 | view::VisibleEntities, 23 | RenderApp, 24 | RenderStage, 25 | {texture::BevyDefault, texture::GpuImage}, 26 | }, 27 | sprite::{ 28 | DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, 29 | SetMesh2dBindGroup, SetMesh2dViewBindGroup, 30 | }, 31 | }; 32 | 33 | use bytemuck::{Pod, Zeroable}; 34 | // use crate::canvas::*; 35 | use crate::inputs::*; 36 | 37 | use crate::plot::*; 38 | 39 | use crate::canvas::ChangeCanvasMaterialEvent; 40 | use crate::util::*; 41 | 42 | // use flo_curves::*; 43 | // use itertools_num::linspace; 44 | 45 | pub fn change_segment_uni( 46 | mut query: Query<&mut SegmentUniform>, 47 | mouse_position: Res, 48 | mouse_button_input: Res>, 49 | ) { 50 | for mut segment_uni in query.iter_mut() { 51 | let mouse_pos = mouse_position.position; 52 | 53 | if mouse_button_input.pressed(MouseButton::Left) { 54 | 55 | // println!("left: {}, right: {}", segment_uni.left, segment_uni.mech); 56 | } else if mouse_button_input.pressed(MouseButton::Right) { 57 | segment_uni.hole_size = mouse_pos.x / 100.0; 58 | segment_uni.segment_size = mouse_pos.x / 100.0; 59 | // segment_uni.ya.x = mouse_pos.x / 100.0; 60 | // segment_uni.ya.y = mouse_pos.y / 100.0; 61 | println!( 62 | "left: {}, right: {}", 63 | segment_uni.segment_size, segment_uni.hole_size 64 | ); 65 | } 66 | } 67 | } 68 | 69 | pub fn segments_setup( 70 | mut commands: Commands, 71 | mut meshes: ResMut>, 72 | mut change_canvas_material_event: EventReader, 73 | mut plots: ResMut>, 74 | query: Query<(Entity, &Handle), With>, 75 | ) { 76 | for event in change_canvas_material_event.iter() { 77 | // 78 | for (entity, plot_handle) in query.iter() { 79 | if event.plot_handle == *plot_handle { 80 | commands.entity(entity).despawn(); 81 | } 82 | } 83 | 84 | let mut plot = plots.get_mut(&event.plot_handle).unwrap(); 85 | 86 | plot_segments( 87 | &mut commands, 88 | &mut meshes, 89 | // ys, 90 | &mut plot, 91 | &event.plot_handle, 92 | ) 93 | } 94 | } 95 | 96 | // Compute derivatives at each point 97 | pub fn make_df(ys: &Vec) -> (Vec, Vec) { 98 | let df0 = (ys[1].y - ys[0].y) / (ys[1].x - ys[0].x); 99 | let mut dfs = vec![df0]; 100 | for i in 1..ys.len() - 1 { 101 | let y_m1 = ys[i - 1]; 102 | // let x0 = ys[i]; 103 | let y1 = ys[i + 1]; 104 | let dfi = (y1.y - y_m1.y) / (y1.x - y_m1.x); 105 | 106 | dfs.push(dfi); 107 | } 108 | 109 | // for the first and last points, we need to extrapolate the first derivative using the second derivative 110 | dfs[0] = dfs[1] - (ys[1].x - ys[0].x) * (dfs[2] - dfs[1]) / (ys[2].x - ys[1].x); 111 | 112 | let la = ys.len() - 1; 113 | let df_final = dfs[la - 1] 114 | - (ys[la - 1].x - ys[la].x) * (dfs[la - 2] - dfs[la - 1]) / (ys[la - 2].x - ys[la - 1].x); 115 | 116 | dfs.push(df_final); 117 | 118 | // derivatives 119 | let dfs_vec2 = dfs 120 | .iter() 121 | .map(|q| Vec2::new(1.0, *q).normalize()) 122 | .collect::>(); 123 | 124 | // normals 125 | let ns_vec2 = dfs 126 | .iter() 127 | .map(|q| Vec2::new(*q, -1.0).normalize()) 128 | .collect::>(); 129 | 130 | return (dfs_vec2, ns_vec2); 131 | } 132 | 133 | pub fn plot_segments( 134 | commands: &mut Commands, 135 | meshes: &mut ResMut>, 136 | plot: &mut Plot, 137 | plot_handle: &Handle, 138 | ) { 139 | let data = plot.data.clone(); 140 | plot.compute_zeros(); 141 | 142 | for segment_plot in data.segment_groups.iter() { 143 | let ys = segment_plot.data.clone(); 144 | 145 | // derivatives and normals 146 | let (dfs, ns) = make_df(&ys); 147 | // println!("dfs: {:?}", ns); 148 | 149 | let num_pts = ys.len(); 150 | 151 | let ys_world = ys.iter().map(|y| plot.to_world(*y)).collect::>(); 152 | 153 | let quad_size = 30.0; 154 | 155 | let mut mesh0 = Vec::new(); 156 | let mut mesh_attr_uvs = Vec::new(); 157 | let mut inds = Vec::new(); 158 | let mut ends = Vec::new(); 159 | let mut mesh_attr_controls: Vec<[f32; 4]> = Vec::new(); 160 | 161 | let line_width = 10.0; 162 | for k in 0..num_pts - 1 { 163 | // if k == 20 || k == 22 || k == 24 || k == 26 || k == 28 || k == 30 { 164 | // continue; 165 | // } 166 | // let quadt_offset = line_width * 1.0; 167 | 168 | // let p0 = Vec2::new(ys_world[k].x - quadt_offset, ys_world[k].y + quadt_offset); 169 | // let p1 = Vec2::new(ys_world[k].x - quadt_offset, ys_world[k].y - quadt_offset); 170 | // let p2 = Vec2::new( 171 | // ys_world[k + 1].x + quadt_offset, 172 | // ys_world[k].y + quadt_offset, 173 | // ); 174 | // let p3 = Vec2::new( 175 | // ys_world[k + 1].x + quadt_offset, 176 | // ys_world[k].y - quadt_offset, 177 | // ); 178 | 179 | let y0 = ys_world[k]; 180 | let y1 = ys_world[k + 1]; 181 | 182 | // let q0 = ys_world[k] - dfs[k] * 10.0; 183 | // let q1 = ys_world[k + 1] + dfs[k + 1] * 10.0; 184 | 185 | // let theta = (y1.x - y0.x).atan2(y1.y - y0.y); 186 | 187 | // let dy = (y1 - y0).normalize(); 188 | // let n = Vec2::new(-dy.y, dy.x); 189 | let n0 = -ns[k]; 190 | let n1 = -ns[k + 1]; 191 | 192 | let p0 = y0 + n0 * line_width; 193 | let p1 = y0 - n0 * line_width; 194 | let p2 = y1 + n1 * line_width; 195 | let p3 = y1 - n1 * line_width; 196 | 197 | // let r = 50.0; 198 | // let p0 = Vec2::new(-r, -r); 199 | // let p1 = Vec2::new(-r, r); 200 | // let p2 = Vec2::new(r, r); 201 | // let p3 = Vec2::new(r, -r); 202 | 203 | mesh0.push(p0); 204 | mesh0.push(p1); 205 | mesh0.push(p2); 206 | mesh0.push(p3); 207 | 208 | ends.push([y0.x, y0.y, y1.x, y1.y]); 209 | ends.push([y0.x, y0.y, y1.x, y1.y]); 210 | ends.push([y0.x, y0.y, y1.x, y1.y]); 211 | ends.push([y0.x, y0.y, y1.x, y1.y]); 212 | 213 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 214 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 215 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 216 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 217 | 218 | mesh_attr_uvs.push([p0.x, p0.y]); 219 | mesh_attr_uvs.push([p1.x, p1.y]); 220 | mesh_attr_uvs.push([p2.x, p2.y]); 221 | mesh_attr_uvs.push([p3.x, p3.y]); 222 | 223 | let ki = k * 4; 224 | 225 | inds.push(ki as u32); 226 | inds.push((ki + 1) as u32); 227 | inds.push((ki + 2) as u32); 228 | 229 | inds.push((ki + 3) as u32); 230 | inds.push((ki + 2) as u32); 231 | inds.push((ki + 1) as u32); 232 | } 233 | 234 | let mut mesh_pos_attributes: Vec<[f32; 3]> = Vec::new(); 235 | // let mut normals = Vec::new(); 236 | // TODO: z position is here 237 | for position in mesh0 { 238 | mesh_pos_attributes.push([position.x, position.y, -30.0]); 239 | // normals.push([0.0, 0.0, 1.0]); 240 | } 241 | 242 | let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); 243 | 244 | mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, mesh_pos_attributes.clone()); 245 | mesh.set_attribute("Ends", ends); 246 | // mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals.clone()); 247 | mesh.set_indices(Some(Indices::U32(inds))); 248 | mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, mesh_attr_uvs); 249 | 250 | mesh.set_attribute("Vertext_Control", mesh_attr_controls); 251 | 252 | commands 253 | .spawn_bundle(( 254 | SegmentMesh2d::default(), 255 | Mesh2dHandle(meshes.add(mesh)), 256 | // Mesh2dHandle(meshes.add(Mesh::from(shape::Quad { 257 | // size: Vec2::splat(50.0), 258 | // flip: false, 259 | // }))), 260 | GlobalTransform::default(), 261 | Transform::from_translation(Vec3::new(0.0, 0.0, 3.0) + plot.position.extend(0.0)), 262 | Visibility::default(), 263 | ComputedVisibility::default(), 264 | )) 265 | .insert(plot_handle.clone()) 266 | .insert(SegmentUniform { 267 | segment_size: segment_plot.size, 268 | hole_size: 1.0, 269 | zoom: 1.0, 270 | 271 | segment_point_color: col_to_vec4(segment_plot.segment_point_color), 272 | color: col_to_vec4(segment_plot.color), 273 | quad_size, 274 | inner_canvas_size_in_pixels: plot.size / (1.0 + plot.outer_border), 275 | canvas_position: plot.position, 276 | contour: if segment_plot.draw_contour { 1.0 } else { 0.0 }, 277 | }); 278 | } 279 | } 280 | 281 | /// A marker component for colored 2d meshes 282 | #[derive(Component, Default)] 283 | pub struct SegmentMesh2d; 284 | 285 | #[derive(Component, Clone, AsStd140)] 286 | pub struct SegmentUniform { 287 | pub segment_size: f32, 288 | pub hole_size: f32, 289 | pub zoom: f32, 290 | pub quad_size: f32, 291 | pub contour: f32, 292 | pub inner_canvas_size_in_pixels: Vec2, 293 | pub canvas_position: Vec2, 294 | pub color: Vec4, 295 | pub segment_point_color: Vec4, 296 | } 297 | 298 | pub struct SegmentMesh2dPipeline { 299 | pub view_layout: BindGroupLayout, 300 | pub mesh_layout: BindGroupLayout, 301 | pub custom_uniform_layout: BindGroupLayout, 302 | 303 | // This dummy white texture is to be used in place of optional textures 304 | pub dummy_white_gpu_image: GpuImage, 305 | pub shader_handle: Handle, 306 | } 307 | 308 | impl FromWorld for SegmentMesh2dPipeline { 309 | fn from_world(world: &mut World) -> Self { 310 | let mesh2d_pipeline = Mesh2dPipeline::from_world(world).clone(); 311 | 312 | let render_device = world.get_resource::().unwrap(); 313 | 314 | let custom_uniform_layout = 315 | render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { 316 | entries: &[BindGroupLayoutEntry { 317 | binding: 0, 318 | visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, 319 | ty: BindingType::Buffer { 320 | ty: BufferBindingType::Uniform, 321 | has_dynamic_offset: true, 322 | min_binding_size: BufferSize::new( 323 | SegmentUniform::std140_size_static() as u64 324 | ), 325 | }, 326 | count: None, 327 | }], 328 | label: Some("custom_uniform_layout"), 329 | }); 330 | 331 | let world = world.cell(); 332 | let asset_server = world.get_resource::().unwrap(); 333 | 334 | let shader_handle = asset_server.load("shaders/segments.wgsl"); 335 | 336 | Self { 337 | view_layout: mesh2d_pipeline.view_layout, 338 | mesh_layout: mesh2d_pipeline.mesh_layout, 339 | custom_uniform_layout, 340 | dummy_white_gpu_image: mesh2d_pipeline.dummy_white_gpu_image, 341 | shader_handle, 342 | } 343 | } 344 | } 345 | 346 | // We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline` 347 | impl SpecializedPipeline for SegmentMesh2dPipeline { 348 | type Key = Mesh2dPipelineKey; 349 | 350 | fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { 351 | // Customize how to store the meshes' vertex attributes in the vertex buffer 352 | // Our meshes only have position and color 353 | let vertex_attributes = vec![ 354 | // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) 355 | VertexAttribute { 356 | format: VertexFormat::Float32x3, 357 | // this offset is the size of the color attribute, which is stored first 358 | offset: 16, 359 | // position is available at location 0 in the shader 360 | shader_location: 0, 361 | }, 362 | // Color 363 | VertexAttribute { 364 | format: VertexFormat::Float32x4, 365 | offset: 0, 366 | shader_location: 1, 367 | }, 368 | // uv 369 | VertexAttribute { 370 | format: VertexFormat::Float32x2, 371 | offset: 28, 372 | shader_location: 2, 373 | }, 374 | // Control Point 375 | VertexAttribute { 376 | format: VertexFormat::Float32x4, 377 | offset: 36, 378 | shader_location: 3, 379 | }, 380 | ]; 381 | // This is the sum of the size of position, color uv attributes (12 + 16 + 8 + 8 = 44) 382 | let vertex_array_stride = 52; 383 | 384 | RenderPipelineDescriptor { 385 | vertex: VertexState { 386 | // Use our custom shader 387 | shader: self.shader_handle.clone(), 388 | entry_point: "vertex".into(), 389 | shader_defs: Vec::new(), 390 | // Use our custom vertex buffer 391 | buffers: vec![VertexBufferLayout { 392 | array_stride: vertex_array_stride, 393 | step_mode: VertexStepMode::Vertex, 394 | attributes: vertex_attributes, 395 | }], 396 | }, 397 | fragment: Some(FragmentState { 398 | // Use our custom shader 399 | shader: self.shader_handle.clone(), 400 | shader_defs: Vec::new(), 401 | entry_point: "fragment".into(), 402 | targets: vec![ColorTargetState { 403 | format: TextureFormat::bevy_default(), 404 | blend: Some(BlendState::ALPHA_BLENDING), 405 | write_mask: ColorWrites::ALL, 406 | }], 407 | }), 408 | // Use the two standard uniforms for 2d meshes 409 | layout: Some(vec![ 410 | // Bind group 0 is the view uniform 411 | self.view_layout.clone(), 412 | // Bind group 1 is the mesh uniform 413 | self.mesh_layout.clone(), 414 | self.custom_uniform_layout.clone(), 415 | // texture 416 | // self.material_layout.clone(), 417 | ]), 418 | primitive: PrimitiveState { 419 | front_face: FrontFace::Ccw, 420 | cull_mode: Some(Face::Back), 421 | unclipped_depth: false, 422 | polygon_mode: PolygonMode::Fill, 423 | conservative: false, 424 | topology: key.primitive_topology(), 425 | strip_index_format: None, 426 | }, 427 | depth_stencil: None, 428 | multisample: MultisampleState { 429 | count: key.msaa_samples(), 430 | mask: !0, 431 | alpha_to_coverage_enabled: false, 432 | }, 433 | label: Some("colored_mesh2d_pipeline".into()), 434 | } 435 | } 436 | } 437 | 438 | // This specifies how to render a colored 2d mesh 439 | type DrawSegmentMesh2d = ( 440 | // Set the pipeline 441 | SetItemPipeline, 442 | // Set the view uniform as bind group 0 443 | SetMesh2dViewBindGroup<0>, 444 | // Set the mesh uniform as bind group 1 445 | SetMesh2dBindGroup<1>, 446 | SetSegmentUniformBindGroup<2>, 447 | // Draw the mesh 448 | DrawMesh2d, 449 | ); 450 | 451 | /// Plugin that renders [`SegmentMesh2d`]s 452 | pub struct SegmentMesh2dPlugin; 453 | 454 | impl Plugin for SegmentMesh2dPlugin { 455 | fn build(&self, app: &mut App) { 456 | app.add_plugin(UniformComponentPlugin::::default()); 457 | 458 | let render_app = app.get_sub_app_mut(RenderApp).unwrap(); 459 | render_app 460 | .add_render_command::() 461 | .init_resource::() 462 | .init_resource::>() 463 | .add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d) 464 | .add_system_to_stage(RenderStage::Queue, queue_customuniform_bind_group) 465 | .add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d); 466 | } 467 | } 468 | 469 | pub fn extract_colored_mesh2d( 470 | mut commands: Commands, 471 | mut previous_len: Local, 472 | query: Query<(Entity, &SegmentUniform, &ComputedVisibility), With>, 473 | ) { 474 | let mut values = Vec::with_capacity(*previous_len); 475 | for (entity, custom_uni, computed_visibility) in query.iter() { 476 | if !computed_visibility.is_visible { 477 | continue; 478 | } 479 | values.push((entity, (custom_uni.clone(), SegmentMesh2d))); 480 | } 481 | *previous_len = values.len(); 482 | commands.insert_or_spawn_batch(values); 483 | } 484 | 485 | pub struct SegmentUniformBindGroup { 486 | pub value: BindGroup, 487 | } 488 | 489 | pub fn queue_customuniform_bind_group( 490 | mut commands: Commands, 491 | mesh2d_pipeline: Res, 492 | render_device: Res, 493 | mesh2d_uniforms: Res>, 494 | ) { 495 | if let Some(binding) = mesh2d_uniforms.uniforms().binding() { 496 | // println!("binding: {:?}", binding); 497 | 498 | commands.insert_resource(SegmentUniformBindGroup { 499 | value: render_device.create_bind_group(&BindGroupDescriptor { 500 | entries: &[BindGroupEntry { 501 | binding: 0, 502 | resource: binding, 503 | }], 504 | label: Some("customuniform_bind_group"), 505 | layout: &mesh2d_pipeline.custom_uniform_layout, 506 | }), 507 | }); 508 | } 509 | } 510 | 511 | #[allow(clippy::too_many_arguments)] 512 | pub fn queue_colored_mesh2d( 513 | transparent_draw_functions: Res>, 514 | colored_mesh2d_pipeline: Res, 515 | mut pipelines: ResMut>, 516 | mut pipeline_cache: ResMut, 517 | msaa: Res, 518 | render_meshes: Res>, 519 | colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With>, 520 | mut views: Query<(&VisibleEntities, &mut RenderPhase)>, 521 | ) { 522 | if colored_mesh2d.is_empty() { 523 | return; 524 | } 525 | // Iterate each view (a camera is a view) 526 | for (visible_entities, mut transparent_phase) in views.iter_mut() { 527 | let draw_colored_mesh2d = transparent_draw_functions 528 | .read() 529 | .get_id::() 530 | .unwrap(); 531 | 532 | let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); 533 | 534 | // Queue all entities visible to that view 535 | for visible_entity in &visible_entities.entities { 536 | if let Ok((mesh2d_handle, mesh2d_uniform)) = colored_mesh2d.get(*visible_entity) { 537 | let mut mesh2d_key = mesh_key; 538 | if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { 539 | mesh2d_key |= 540 | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); 541 | } 542 | 543 | let pipeline_id = 544 | pipelines.specialize(&mut pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key); 545 | 546 | let mesh_z = mesh2d_uniform.transform.w_axis.z; 547 | transparent_phase.add(Transparent2d { 548 | entity: *visible_entity, 549 | draw_function: draw_colored_mesh2d, 550 | pipeline: pipeline_id, 551 | sort_key: FloatOrd(mesh_z), 552 | batch_range: None, 553 | }); 554 | } 555 | } 556 | } 557 | } 558 | 559 | pub struct SetSegmentUniformBindGroup; 560 | impl EntityRenderCommand for SetSegmentUniformBindGroup { 561 | type Param = ( 562 | SRes, 563 | SQuery>>, 564 | ); 565 | #[inline] 566 | fn render<'w>( 567 | _view: Entity, 568 | item: Entity, 569 | (mesh2d_bind_group, mesh2d_query): SystemParamItem<'w, '_, Self::Param>, 570 | pass: &mut TrackedRenderPass<'w>, 571 | ) -> RenderCommandResult { 572 | let mesh2d_index = mesh2d_query.get(item).unwrap(); 573 | 574 | pass.set_bind_group( 575 | I, 576 | &mesh2d_bind_group.into_inner().value, 577 | &[mesh2d_index.index()], 578 | ); 579 | RenderCommandResult::Success 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/canvas/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/src/canvas/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/canvas/canvas.wgsl: -------------------------------------------------------------------------------- 1 | 2 | #import bevy_sprite::mesh2d_types 3 | #import bevy_sprite::mesh2d_view_types 4 | 5 | struct Vertex { 6 | @location(0) position: vec3, 7 | @location(1) normal: vec3, 8 | @location(2) uv: vec2, 9 | #ifdef VERTEX_TANGENTS 10 | @location(3) tangent: vec4, 11 | #endif 12 | }; 13 | 14 | struct VertexOutput { 15 | @builtin(position) clip_position: vec4, 16 | @location(0) world_position: vec4, 17 | @location(1) world_normal: vec3, 18 | @location(2) uv: vec2, 19 | #ifdef VERTEX_TANGENTS 20 | @location(3) world_tangent: vec4, 21 | #endif 22 | }; 23 | 24 | 25 | struct GraphEditShader { 26 | mouse_pos: vec2, 27 | tick_period: vec2, 28 | bound_up: vec2, // TODO 29 | bound_lo: vec2, 30 | time: f32, 31 | zoom: f32, 32 | size: vec2, 33 | outer_border: vec2, 34 | position: vec2, 35 | show_target: f32, 36 | hide_contour: f32, 37 | target_pos: vec2, 38 | background_color1: vec4, 39 | background_color2: vec4, 40 | target_color: vec4, 41 | show_grid: f32, 42 | show_axes: f32, 43 | }; 44 | 45 | @group(0) @binding(0) 46 | var view: View; 47 | 48 | @group(1) @binding(0) 49 | var mate: GraphEditShader; 50 | 51 | @group(2) @binding(0) 52 | var mesh: Mesh2d; 53 | 54 | // struct VertexOutput { 55 | // @builtin(position) clip_position: vec4, 56 | // @location(0) uv: vec2, 57 | // }; 58 | 59 | struct Segment { 60 | start: vec2, 61 | end: vec2, 62 | }; 63 | 64 | struct Interval { 65 | start: vec2, 66 | end: vec2, 67 | control: vec4, 68 | }; 69 | 70 | struct GraphSize { 71 | size: vec2, 72 | outer_border: vec2, 73 | }; 74 | 75 | 76 | 77 | var solid: f32 = 0.001; 78 | var smooth_dist2: f32 = 0.003; 79 | var point2_radius: f32 = 0.03; 80 | var out_of_bounds: f32 = 100000.0; 81 | var bluish : vec4 = vec4(0.13, 0.28, 0.86, 1.0); 82 | var num_segments: i32 = 256; 83 | 84 | 85 | 86 | struct Globals { 87 | time: f32, 88 | zoom: f32, 89 | dum1: f32, 90 | dum2: f32, 91 | }; 92 | 93 | 94 | 95 | 96 | @vertex 97 | fn vertex(vertex: Vertex) -> VertexOutput { 98 | let world_position = mesh.model * vec4(vertex.position, 1.0); 99 | 100 | var out: VertexOutput; 101 | out.uv = world_position.xy; 102 | out.uv = vertex.uv; 103 | // out.world_position = world_position; 104 | out.clip_position = view.view_proj * world_position; 105 | out.world_normal = mat3x3( 106 | mesh.inverse_transpose_model[0].xyz, 107 | mesh.inverse_transpose_model[1].xyz, 108 | mesh.inverse_transpose_model[2].xyz 109 | ) * vertex.normal; 110 | #ifdef VERTEX_TANGENTS 111 | out.world_tangent = vec4( 112 | mat3x3( 113 | mesh.model[0].xyz, 114 | mesh.model[1].xyz, 115 | mesh.model[2].xyz 116 | ) * vertex.tangent.xyz, 117 | vertex.tangent.w 118 | ); 119 | #endif 120 | return out; 121 | } 122 | 123 | struct FragmentInput { 124 | @builtin(front_facing) is_front: bool, 125 | @location(0) world_position: vec4, 126 | @location(1) world_normal: vec3, 127 | @location(2) uv: vec2, 128 | #ifdef VERTEX_TANGENTS 129 | @location(3) world_tangent: vec4, 130 | #endif 131 | }; 132 | 133 | fn from_pix_to_local(uv_orig: vec2) -> vec2 { 134 | 135 | var uv = (uv_orig - mate.position) ; 136 | 137 | let x_max = mate.bound_up.x; 138 | let y_max = mate.bound_up.y; 139 | 140 | let x_min = mate.bound_lo.x; 141 | let y_min = mate.bound_lo.y; 142 | 143 | let x_range = x_max - x_min; 144 | let y_range = y_max - y_min; 145 | 146 | uv.x = uv.x * (1.0 + mate.outer_border.x) / mate.size.x ; 147 | uv.x = uv.x * x_range ; 148 | 149 | uv.y = uv.y * (1.0 + mate.outer_border.y) / mate.size.y; 150 | uv.y = uv.y * y_range; 151 | 152 | let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 153 | let uv_local = uv + current_zero_pos; 154 | 155 | return uv_local; 156 | } 157 | 158 | // fn from_pix_to_local(uv_orig: vec2) -> vec2 { 159 | 160 | // // var uv = (uv_orig - mate.position) ; 161 | // var uv = (uv_orig - vec2(0.5, 0.5)) ; 162 | 163 | // let x_max = mate.bound_up.x; 164 | // let y_max = mate.bound_up.y; 165 | 166 | // let x_min = mate.bound_lo.x; 167 | // let y_min = mate.bound_lo.y; 168 | 169 | // let x_range = x_max - x_min; 170 | // let y_range = y_max - y_min; 171 | 172 | // uv.x = uv.x * (1.0 + mate.outer_border.x) ; 173 | // uv.x = uv.x * x_range ; 174 | 175 | // uv.y = uv.y * (1.0 + mate.outer_border.y); 176 | // uv.y = -uv.y * y_range; 177 | 178 | // let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 179 | // let uv_local = uv + current_zero_pos; 180 | 181 | // return uv_local; 182 | // } 183 | 184 | fn from_local_to_pixels(uv_local: vec2) -> vec2 { 185 | var uv = uv_local; 186 | 187 | uv.x = uv.x * mate.size.x / (1.0 + mate.outer_border.x) ; 188 | uv.x = uv.x / (mate.bound_up.x - mate.bound_lo.x); 189 | 190 | uv.y = uv.y * mate.size.y / (1.0 + mate.outer_border.y) ; 191 | uv.y = uv.y / (mate.bound_up.y - mate.bound_lo.y); 192 | 193 | 194 | 195 | return uv; 196 | } 197 | 198 | fn from_local_to_pixels3(uv_local: vec2) -> vec2 { 199 | var uv = uv_local; 200 | 201 | uv.x = (uv.x - 0.5) * mate.size.x ; 202 | // uv.x = uv.x / (mate.bound_up.x - mate.bound_lo.x); 203 | 204 | uv.y = -(uv.y - 0.5) * mate.size.y ; 205 | // uv.y = uv.y / (mate.bound_up.y - mate.bound_lo.y); 206 | 207 | uv = uv + mate.position / 2.0; 208 | 209 | 210 | 211 | return uv; 212 | } 213 | 214 | // fn from_uv_to_pixels2(uv_local: vec2) -> vec2 { 215 | // var uv = uv_local; 216 | 217 | // let x_max = mate.bound_up.x; 218 | // let y_max = mate.bound_up.y; 219 | 220 | // let x_min = mate.bound_lo.x; 221 | // let y_min = mate.bound_lo.y; 222 | 223 | // let x_range = x_max - x_min; 224 | // let y_range = y_max - y_min; 225 | 226 | // let current_zero_pos = vec2(x_range / 2.0 + x_min, y_range / 2.0 + y_min); 227 | // uv = uv - current_zero_pos; 228 | 229 | // uv.y = uv.y / y_range; 230 | // uv.y = uv.y / (1.0 + mate.outer_border.y) * mate.size.y; 231 | 232 | // uv.x = uv.x / x_range; 233 | // uv.x = uv.x / (1.0 + mate.outer_border.x) * mate.size.x; 234 | 235 | // return uv; 236 | // } 237 | 238 | // There are currently no function for x % 2 in wgpu 239 | fn even(uv: f32) -> f32 { 240 | var tempo: f32 = 0.0; 241 | let whatever = modf(uv + 1.0, &tempo); 242 | var temp2 = 0.; 243 | let frac = modf(tempo / 2.0, &temp2); 244 | 245 | if abs(frac) < 0.001 { 246 | return 1.0; 247 | } else { 248 | return 0.0; 249 | } 250 | } 251 | 252 | //////////////////////// sdfs ////////////////////////////////////// 253 | 254 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 255 | var x = r.x; 256 | var y = r.y; 257 | x = select(r.z, r.x, p.x > 0.); 258 | y = select(r.w, r.y, p.x > 0.); 259 | x = select(y, x, p.y > 0.); 260 | let q = abs(p) - b + x; 261 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 262 | } 263 | 264 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 265 | let pa: vec2 = p - a; 266 | let ba: vec2 = b - a; 267 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); 268 | return length(pa - ba * h); 269 | } 270 | 271 | // fn draw_segment(thickness: f32, rect: vec4, uv: vec2, segment: Segment, color: vec4, alpha: f32 ) -> vec4 { 272 | // let t = thickness; 273 | // let d = sdSegment(uv, segment.start, segment.end); 274 | // let seg_start = smoothstep(t, t + 1.0/100.0, d); 275 | // let rect2 = mix(rect, color, alpha*abs( 1.0 -seg_start)); 276 | // return rect2; 277 | // } 278 | 279 | fn draw_segment(thickness: f32, rect: vec4, uv: vec2, segment: Segment, color: vec4, alpha: f32) -> vec4 { 280 | // let uv = from_local_to_pixels(uv_orig); 281 | let t = thickness; // * mate.globals.zoom; 282 | let d = sdSegment(uv, segment.start, segment.end); 283 | let seg_start = smoothstep(t, t + t * 2.0, d); 284 | let rect2 = mix(rect, color, alpha * abs(1.0 - seg_start)); 285 | return rect2; 286 | } 287 | 288 | 289 | fn sdCircle(pos: vec2, r: f32) -> f32 { 290 | return length(pos) - r; 291 | } 292 | 293 | fn draw_circle( 294 | rect: vec4, 295 | uv_orig: vec2, 296 | r: f32, 297 | pcolor: vec4, 298 | annular: bool, 299 | point2: vec2, 300 | ) -> vec4 { 301 | 302 | let t = solid * 100.0; 303 | let s = smooth_dist2 * 200.0; 304 | 305 | let uv_pixels = from_local_to_pixels(uv_orig - point2); 306 | let r_pixels_vec2 = from_local_to_pixels(vec2(r, r)); 307 | 308 | var sd_start = sdCircle(uv_pixels, r_pixels_vec2.x); 309 | // 310 | if annular { 311 | sd_start = abs(sd_start); 312 | } 313 | let cerc_start = smoothstep(t, t + s * 2., sd_start); 314 | let rect2 = mix(rect, pcolor, 1.0 - cerc_start); 315 | return rect2; 316 | } 317 | //////////////////////// sdfs ////////////////////////////////////// 318 | 319 | 320 | 321 | 322 | 323 | @fragment 324 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 325 | 326 | // ///////////////////// coordinates ///////////////////////////////// 327 | let x_max = mate.bound_up.x; 328 | let y_max = mate.bound_up.y; 329 | 330 | let x_min = mate.bound_lo.x; 331 | let y_min = mate.bound_lo.y; 332 | 333 | let x_range = x_max - x_min; 334 | let y_range = y_max - y_min; 335 | // ///////////////////// coordinates ///////////////////////////////// 336 | 337 | let uv_pix = from_local_to_pixels3(in.uv); 338 | 339 | var uv = from_pix_to_local(uv_pix) ; 340 | // var uv = in.uv; 341 | // var uv = uv_pix; 342 | // var uv = in.world_position.xy; 343 | 344 | // if uv_pix.x > 0.5 { 345 | // return vec4(0.0, 0.0, 0.0, 0.0); 346 | // } 347 | 348 | 349 | 350 | ///////////////////// colors ///////////////////////////////////// 351 | let red = vec4(1.0, 0.0, 0.0, 1.0); 352 | let yellow = vec4(0.89, 0.41, 0.14, 1.0); 353 | let green = vec4(0.0, 1.0, 0.0, 1.0); 354 | let black = vec4(0.0, 0.0, 0.0, 1.0); 355 | 356 | let colBackground1 = mate.background_color1; 357 | let colBackground2 = mate.background_color2; 358 | ///////////////////// colors ///////////////////// 359 | 360 | 361 | ///////////////////// background ///////////////// 362 | let tile_freq_x: f32 = 1.0 / mate.tick_period.x; 363 | let tile_freq_y: f32 = 1.0 / mate.tick_period.y; 364 | 365 | let tiles = even((floor(tile_freq_x * uv.x) + floor(tile_freq_y * uv.y))) ; //+ even(uv.y * 5.); 366 | 367 | var rect: vec4 = mix(colBackground1, colBackground2, tiles); 368 | ///////////////////// background ///////////////// 369 | 370 | 371 | 372 | ////////////////////////////////// grid //////////////////////////////// 373 | let so = mate.size / (1.0 + mate.outer_border); 374 | let edges = vec2(0.5, 0.5) * so; 375 | 376 | let origin = (-mate.bound_lo / (mate.bound_up - mate.bound_lo) - 0.5) * so; 377 | 378 | let tick_period_pix = mate.tick_period / (mate.bound_up - mate.bound_lo) * so; 379 | let bar_alpha = 1.0; 380 | 381 | var segment: Segment; 382 | 383 | var sig = sign(uv); 384 | sig = vec2(1.0, 1.0); 385 | 386 | // in the tiki coordinate, 1 corresponds to one tick period 387 | let tiki = (uv_pix - mate.position - origin) / tick_period_pix - vec2(0.5, 0.5) * sig ; 388 | 389 | // In wgpu currently, the mod function take a reference to a dummy variable. 390 | // This will change in the future. 391 | var temp_y: f32 = 0.0; 392 | var temp_x: f32 = 0.0; 393 | let sad_x = modf(tiki.x, &temp_x); 394 | let sad_y = modf(tiki.y, &temp_y); 395 | 396 | 397 | let ggg = -vec2(sad_x, sad_y) ; 398 | 399 | 400 | let half = -0.5 * sig; 401 | 402 | let aspect_ratio = tick_period_pix.x / tick_period_pix.y; 403 | 404 | let bars_thickness = 0.5 / tick_period_pix ; 405 | 406 | if mate.show_grid > 0.5 { 407 | // horizontal bars 408 | segment.start = vec2(-edges.x, half.y) ; 409 | segment.end = vec2(edges.x, half.y) ; 410 | rect = draw_segment(bars_thickness.y, rect, ggg, segment, black, bar_alpha) ; 411 | 412 | // vertical bars 413 | segment.start = vec2(half.x, -edges.y) ; 414 | segment.end = vec2(half.x, edges.y) ; 415 | rect = draw_segment(bars_thickness.x, rect, ggg, segment, black, bar_alpha) ; 416 | } 417 | /////////////////////////////////////// grid ///////////////////////////////////// 418 | 419 | 420 | 421 | /////////////////////////////////////// axes ////////////////////////////// 422 | if mate.show_axes > 0.5 { 423 | segment.start = vec2(-edges.x, origin.y); 424 | segment.end = vec2(edges.x, origin.y); 425 | rect = draw_segment(1.0, rect, uv_pix - mate.position, segment, black, bar_alpha) ; 426 | 427 | 428 | segment.start = vec2(origin.x, -edges.y); 429 | segment.end = vec2(origin.x, edges.y); 430 | rect = draw_segment(1.0, rect, uv_pix - mate.position, segment, black, bar_alpha) ; 431 | } 432 | //////////////////////////////////////// axes ////////////////////////////// 433 | 434 | 435 | 436 | /////////////////// borders ///////////////////////// 437 | rect = mix(rect, colBackground2, step(x_max, uv.x)); 438 | rect = mix(rect, colBackground2, step(-x_min, -uv.x)); 439 | rect = mix(rect, colBackground2, step(-y_min, -uv.y)); 440 | rect = mix(rect, colBackground2, step(y_max, uv.y)); 441 | /////////////////// borders ///////////////////////// 442 | 443 | 444 | /////////////////// mouse target ///////////////////////// 445 | if mate.show_target > 0.5 { 446 | let aspect_ratio = mate.size.y / mate.size.x; 447 | 448 | let target_thickness = 0.75; // mate.globals.zoom; 449 | let pos_edges = edges - mate.position; 450 | 451 | segment.start = vec2(mate.target_pos.x, -pos_edges.y); 452 | segment.end = vec2(mate.target_pos.x, pos_edges.y); 453 | rect = draw_segment(target_thickness, rect, uv_pix, segment, mate.target_color, bar_alpha); 454 | 455 | segment.start = vec2(-pos_edges.x, mate.target_pos.y); 456 | segment.end = vec2(pos_edges.x, mate.target_pos.y); 457 | rect = draw_segment(target_thickness, rect, uv_pix, segment, mate.target_color, bar_alpha); 458 | } 459 | /////////////////// mouse target ///////////////////////// 460 | 461 | 462 | /////////////////// contours ///////////////////////// 463 | if mate.hide_contour < 0.5 { 464 | 465 | let so = mate.size / (1.0 + mate.outer_border); 466 | let ax_thick = 0.8 ; 467 | 468 | let r = 0.02 * so.x; 469 | let d = sdRoundedBox(uv_pix - mate.position, so / 2.0, vec4(r, r, r, r)); 470 | let s = smoothstep(0.0, 2.0, d); 471 | 472 | let colBackground3 = vec4(colBackground2.xyz, 0.0); 473 | rect = mix(rect, colBackground3, s); 474 | 475 | let r = 0.02 * so.x; 476 | let d = sdRoundedBox(uv_pix - mate.position, so / 2.0, vec4(r, r, r, r)); 477 | let s = smoothstep(0.0, 2.0, abs(d) - 1.0); 478 | 479 | rect = mix(rect, vec4(0.0, 0.0, 0.0, 1.0), 1.0 - s); 480 | } 481 | /////////////////// contours ///////////////////////// 482 | 483 | 484 | 485 | return rect; 486 | } 487 | -------------------------------------------------------------------------------- /src/canvas/mod.rs: -------------------------------------------------------------------------------- 1 | // pub mod canvas; 2 | pub mod canvas_actions; 3 | #[allow(unused_imports)] 4 | pub use canvas_actions::*; 5 | 6 | // use bevy::{ 7 | // ecs::system::{lifetimeless::SRes, SystemParamItem}, 8 | // prelude::*, 9 | // reflect::TypeUuid, 10 | // render::{ 11 | // extract_resource::ExtractResource, 12 | // render_asset::{PrepareAssetError, RenderAsset}, 13 | // render_resource::*, 14 | // renderer::RenderDevice, 15 | // }, 16 | // sprite::{Material2d, Material2dPipeline, Material2dPlugin}, 17 | // }; 18 | 19 | use crate::plot::*; 20 | use crate::util::*; 21 | use bevy::prelude::*; 22 | use bevy::reflect::TypePath; 23 | use bevy::render::render_resource::AsBindGroup; 24 | use bevy::render::render_resource::*; 25 | use bevy::sprite::{Material2d, Material2dPlugin}; 26 | use bevy::window::SystemCursorIcon; 27 | use bevy::winit::cursor::CursorIcon; 28 | 29 | #[derive(Component)] 30 | pub(crate) struct PlotLabel; 31 | 32 | #[derive(Component)] 33 | pub(crate) struct TargetLabel; 34 | 35 | #[derive(Event)] 36 | pub(crate) struct SpawnGraphEvent { 37 | pub plot_id: PlotId, 38 | pub canvas: CanvasParams, 39 | } 40 | 41 | pub(crate) enum Corner { 42 | TopLeft, 43 | TopRight, 44 | BottomLeft, 45 | BottomRight, 46 | } 47 | 48 | // TODO: unimplemented 49 | #[derive(Component)] 50 | pub(crate) struct ResizePlotWindow { 51 | pub corner: Corner, 52 | #[allow(dead_code)] 53 | pub previous_position: Vec2, 54 | pub previous_scale: Vec2, 55 | } 56 | 57 | #[derive(Component, Clone)] 58 | pub(crate) struct CanvasParams { 59 | pub position: Vec2, 60 | #[allow(dead_code)] 61 | pub previous_position: Vec2, 62 | pub original_size: Vec2, 63 | pub scale: Vec2, 64 | pub previous_scale: Vec2, 65 | pub hover_radius: f32, 66 | } 67 | 68 | impl CanvasParams { 69 | pub(crate) fn within_rect(&self, position: Vec2) -> bool { 70 | let size = self.original_size * self.scale; 71 | if position.x < self.position.x + size.x / 2.0 72 | && position.x > self.position.x - size.x / 2.0 73 | && position.y < self.position.y + size.y / 2.0 74 | && position.y > self.position.y - size.y / 2.0 75 | { 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | pub(crate) fn clicked_on_plot_corner(&self, position: Vec2, commands: &mut Commands, entity: Entity) { 82 | let size = self.original_size * self.scale; 83 | let top_right = self.position + Vec2::new(size.x / 2.0, size.y / 2.0); 84 | let bottom_left = self.position + Vec2::new(-size.x / 2.0, -size.y / 2.0); 85 | let top_left = self.position + Vec2::new(-size.x / 2.0, size.y / 2.0); 86 | let bottom_right = self.position + Vec2::new(size.x / 2.0, -size.y / 2.0); 87 | 88 | // println!("{:?}", position); 89 | // println!("top_left: {:?}", top_left); 90 | 91 | if (top_right - position).length() < self.hover_radius { 92 | commands.entity(entity).insert(ResizePlotWindow { 93 | corner: Corner::TopRight, 94 | // previous_size: size, 95 | previous_position: self.position, 96 | previous_scale: self.scale, 97 | }); 98 | } 99 | 100 | if (bottom_left - position).length() < self.hover_radius { 101 | commands.entity(entity).insert(ResizePlotWindow { 102 | corner: Corner::BottomLeft, 103 | // previous_size: size, 104 | previous_position: self.position, 105 | previous_scale: self.scale, 106 | }); 107 | } 108 | 109 | if (top_left - position).length() < self.hover_radius { 110 | commands.entity(entity).insert(ResizePlotWindow { 111 | corner: Corner::TopLeft, 112 | // previous_size: self.size, 113 | previous_position: self.position, 114 | previous_scale: self.scale, 115 | }); 116 | println!("top left"); 117 | } 118 | 119 | if (bottom_right - position).length() < self.hover_radius { 120 | commands.entity(entity).insert(ResizePlotWindow { 121 | corner: Corner::BottomRight, 122 | // previous_size: self.size, 123 | previous_position: self.position, 124 | previous_scale: self.scale, 125 | }); 126 | } 127 | } 128 | 129 | pub(crate) fn hovered_on_plot_edges(&self, position: Vec2, window_entity: Entity, mut commands: &mut Commands) { 130 | let size = self.original_size * self.scale; 131 | 132 | let top_right = self.position + Vec2::new(size.x / 2.0, size.y / 2.0); 133 | let bottom_left = self.position + Vec2::new(-size.x / 2.0, -size.y / 2.0); 134 | let top_left = self.position + Vec2::new(-size.x / 2.0, size.y / 2.0); 135 | let bottom_right = self.position + Vec2::new(size.x / 2.0, -size.y / 2.0); 136 | 137 | let mut set_to_default_cursor = true; 138 | 139 | if (top_left - position).length() < self.hover_radius { 140 | commands 141 | .entity(window_entity) 142 | .insert(CursorIcon::System(SystemCursorIcon::NwResize)); 143 | set_to_default_cursor = false; 144 | } 145 | 146 | if (top_right - position).length() < self.hover_radius { 147 | commands 148 | .entity(window_entity) 149 | .insert(CursorIcon::System(SystemCursorIcon::NeResize)); 150 | set_to_default_cursor = false; 151 | } 152 | 153 | if (bottom_left - position).length() < self.hover_radius { 154 | commands 155 | .entity(window_entity) 156 | .insert(CursorIcon::System(SystemCursorIcon::SwResize)); 157 | set_to_default_cursor = false; 158 | } 159 | 160 | if (bottom_right - position).length() < self.hover_radius { 161 | commands 162 | .entity(window_entity) 163 | .insert(CursorIcon::System(SystemCursorIcon::SeResize)); 164 | set_to_default_cursor = false; 165 | } 166 | 167 | if set_to_default_cursor { 168 | commands 169 | .entity(window_entity) 170 | .insert(CursorIcon::System(SystemCursorIcon::Default)); 171 | } 172 | } 173 | } 174 | 175 | #[derive(Component)] 176 | pub(crate) struct MoveAxes; 177 | 178 | #[derive(Component)] 179 | pub(crate) struct ZoomAxes { 180 | pub wheel_dir: f32, 181 | #[allow(dead_code)] 182 | pub mouse_pos: Vec2, 183 | } 184 | 185 | #[derive(Event)] 186 | pub(crate) struct UpdatePlotLabelsEvent { 187 | pub plot_id: PlotId, 188 | pub canvas_entity: Entity, 189 | } 190 | 191 | #[derive(Event)] 192 | pub(crate) struct UpdateTargetLabelEvent { 193 | pub plot_id: PlotId, 194 | pub canvas_entity: Entity, 195 | pub canvas_material_handle: MeshMaterial2d, 196 | // pub mouse_pos: Vec2, 197 | } 198 | 199 | #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] 200 | pub(crate) struct CanvasMaterial { 201 | #[uniform(1)] 202 | pub position: Vec2, 203 | 204 | /// Mouse position in the reference frame of the graph, corresponding to its axes coordinates 205 | #[uniform(0)] 206 | pub mouse_pos: Vec2, 207 | #[uniform(0)] 208 | pub tick_period: Vec2, 209 | 210 | /// Extreme points of the canvas 211 | #[uniform(0)] 212 | pub bound_up: Vec2, 213 | #[uniform(0)] 214 | pub bound_lo: Vec2, 215 | 216 | #[uniform(0)] 217 | pub time: f32, 218 | #[uniform(0)] 219 | pub zoom: f32, 220 | #[uniform(0)] 221 | pub size: Vec2, 222 | #[uniform(0)] 223 | pub outer_border: Vec2, 224 | 225 | #[uniform(0)] 226 | pub show_target: f32, 227 | #[uniform(0)] 228 | pub hide_contour: f32, 229 | #[uniform(0)] 230 | pub target_pos: Vec2, 231 | 232 | #[uniform(0)] 233 | pub background_color1: Vec4, 234 | #[uniform(0)] 235 | pub background_color2: Vec4, 236 | #[uniform(0)] 237 | pub target_color: Vec4, 238 | 239 | #[uniform(0)] 240 | pub show_grid: f32, 241 | #[uniform(0)] 242 | pub show_axes: f32, 243 | } 244 | 245 | impl CanvasMaterial { 246 | pub fn new(plot: &Plot) -> Self { 247 | CanvasMaterial { 248 | mouse_pos: Vec2::ZERO, 249 | tick_period: plot.tick_period, 250 | // bounds: plot.bounds.clone(), 251 | bound_up: plot.bounds.up, 252 | bound_lo: plot.bounds.lo, 253 | time: 0.0, 254 | zoom: 1.0, 255 | size: plot.canvas_size, 256 | outer_border: plot.outer_border, 257 | position: plot.canvas_position, 258 | show_target: if plot.show_target && plot.target_toggle { 259 | 1.0 260 | } else { 261 | 0.0 262 | }, 263 | hide_contour: if plot.hide_contour { 1.0 } else { 0.0 }, 264 | target_pos: Vec2::ZERO, 265 | background_color1: col_to_vec4(plot.background_color1), 266 | background_color2: col_to_vec4(plot.background_color2), 267 | target_color: col_to_vec4(plot.target_color), 268 | show_grid: if plot.show_grid { 1.0 } else { 0.0 }, 269 | show_axes: if plot.show_axes { 1.0 } else { 0.0 }, 270 | } 271 | } 272 | 273 | /// Updates all the shader parameters except the mouse_pos, which is updated every frame anyway. 274 | pub fn update_all(&mut self, plot: &Plot) { 275 | // mouse_pos is supposed to be in World coordinates // self.mouse_pos = plot.plot_coord_mouse_pos; 276 | 277 | self.position = plot.canvas_position; 278 | self.tick_period = plot.tick_period; 279 | 280 | self.bound_up = plot.bounds.up; 281 | self.bound_lo = plot.bounds.lo; 282 | self.zoom = plot.zoom; 283 | self.time = plot.time; 284 | self.size = plot.canvas_size; 285 | self.outer_border = plot.outer_border; 286 | self.show_target = if plot.show_target && plot.target_toggle { 287 | 1.0 288 | } else { 289 | 0.0 290 | }; 291 | // let v = Vec2::new(.x, plot.target_position.y); 292 | self.target_pos = plot.to_local(plot.target_position) + plot.canvas_position; 293 | 294 | self.background_color1 = col_to_vec4(plot.background_color1); 295 | self.background_color2 = col_to_vec4(plot.background_color2); 296 | self.target_color = col_to_vec4(plot.target_color); 297 | self.show_grid = if plot.show_grid { 1.0 } else { 0.0 }; 298 | self.show_axes = if plot.show_axes { 1.0 } else { 0.0 }; 299 | } 300 | 301 | /// Checks whether position is inside the plot bounderies or not. 302 | pub fn within_rect(&self, position: Vec2) -> bool { 303 | let size = self.size; 304 | if position.x < self.position.x + size.x / 2.0 305 | && position.x > self.position.x - size.x / 2.0 306 | && position.y < self.position.y + size.y / 2.0 307 | && position.y > self.position.y - size.y / 2.0 308 | { 309 | return true; 310 | } 311 | return false; 312 | } 313 | } 314 | 315 | // impl bevy::sprite::Material2d for CanvasMaterial { 316 | // fn fragment_shader() -> ShaderRef { 317 | // let handle_untyped = CANVAS_SHADER_HANDLE.clone(); 318 | // let shader_handle: Handle = handle_untyped.typed::(); 319 | // shader_handle.into() 320 | // } 321 | // } 322 | 323 | // Use hot-reload from file OR inline your wgsl. If using a path: 324 | impl Material2d for CanvasMaterial { 325 | fn fragment_shader() -> ShaderRef { 326 | // For example if you keep canvas.wgsl in assets/canvas/canvas.wgsl: 327 | "shaders/canvas.wgsl".into() 328 | } 329 | 330 | fn alpha_mode(&self) -> bevy::sprite::AlphaMode2d { 331 | bevy::sprite::AlphaMode2d::Blend 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/inputs.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | // input::mouse::{MouseMotion, MouseWheel}, 3 | prelude::*, 4 | render::camera::OrthographicProjection, 5 | // sprite::{MaterialMesh2dBundle, Mesh2dHandle}, 6 | }; 7 | 8 | // use crate::canvas::*; 9 | // use crate::markers::SpawnMarkersEvent; 10 | // use crate::util::*; 11 | 12 | #[derive(Resource)] 13 | pub(crate) struct Cursor { 14 | pub position: Vec2, 15 | pub pos_relative_to_click: Vec2, 16 | pub last_click_position: Vec2, 17 | } 18 | 19 | impl Default for Cursor { 20 | fn default() -> Self { 21 | Cursor { 22 | position: Vec2::ZERO, 23 | pos_relative_to_click: Vec2::ZERO, 24 | last_click_position: Vec2::ZERO, 25 | } 26 | } 27 | } 28 | 29 | pub(crate) fn record_mouse_events_system( 30 | mut cursor_moved_events: EventReader, 31 | mouse_button_input: Res>, 32 | mut cursor_res: ResMut, 33 | // mut windows: ResMut, 34 | window: Single<&Window>, 35 | cam_transform_query: Query<&Transform, With>, 36 | cam_ortho_query: Query<&OrthographicProjection>, 37 | ) { 38 | for event in cursor_moved_events.read() { 39 | let cursor_in_pixels = event.position; // lower left is origin 40 | 41 | let window_size = Vec2::new(window.width(), window.height()); 42 | 43 | let screen_position = cursor_in_pixels - window_size / 2.0; 44 | 45 | let cam_transform = cam_transform_query.iter().next().unwrap(); 46 | 47 | // this variable currently has no effect 48 | let mut scale = 1.0; 49 | 50 | for ortho in cam_ortho_query.iter() { 51 | scale = ortho.scale; 52 | } 53 | 54 | // let cursor_vec4: Vec4 = cam_transform.compute_matrix() 55 | // * screen_position.extend(0.0).extend(1.0 / (scale)) 56 | // * scale; 57 | 58 | // let cursor_vec4: Mat4 = cam_transform.compute_matrix() * screen_position.extend(0.0).extend(1.0); 59 | // let cursor_vec4: Vec4 = cam_transform.compute_matrix() * screen_position.extend(0.0).extend(1.0); 60 | 61 | let cursor_vec4 = cam_transform 62 | .compute_matrix() 63 | .mul_vec4(screen_position.extend(0.0).extend(1.0 / scale)) 64 | * scale; 65 | 66 | let cursor_pos = Vec2::new(cursor_vec4.x, cursor_vec4.y); 67 | // let cursor_pos = Vec2::new(cursor_vec4.x, cursor_vec4.y) / cursor_vec4.w; 68 | 69 | cursor_res.position = cursor_pos; 70 | cursor_res.pos_relative_to_click = cursor_res.position - cursor_res.last_click_position; 71 | } 72 | 73 | if mouse_button_input.just_pressed(MouseButton::Left) { 74 | cursor_res.last_click_position = cursor_res.position; 75 | cursor_res.pos_relative_to_click = Vec2::ZERO; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Plotting library for the Bevy game engine. To quickly get started, run a Bevy `App`, add the 2 | //! [`PlotPlugin`] to the `App`, instantiate a [`Plot`] struct, and either use the 3 | //! * [`Plot::plot`]`(my_data: impl `[`Plotable`]`)` method for a regular graph, the 4 | //! * [`Plot::plotm`]`(my_data: impl `[`Plotable`]`)` method for a scatter plot (or plot with markers), or the 5 | //! * [`Plot::plot_func`]`(my_function: fn(f32, f32) -> 32)` method that supports plotting of explicit functions. 6 | //! 7 | //! The `my_data` argument of either of the first two methods has to implement the [`Plotable`] trait 8 | //! (e.g. `Vec`, `Vec<(f32, f32)>`, `Vec`, etc.). In the third option, 9 | //! `my_function` is an explicit function that takes two arguments (x and time) and returns a `f32`. 10 | //! 11 | //! The following code can be found in examples/minimal.rs: 12 | //! ``` 13 | //! use bevy::prelude::*; 14 | //! use bevy_plot::*; 15 | //! 16 | //! fn main() { 17 | //! App::new() 18 | //! .add_plugins(DefaultPlugins) 19 | //! .add_plugin(PlotPlugin) 20 | //! .add_startup_system(setup) 21 | //! .run(); 22 | //! } 23 | //! 24 | //! fn setup(mut commands: Commands, mut plots: ResMut>) { 25 | //! commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 26 | //! 27 | //! let mut plot = Plot::default(); 28 | //! 29 | //! let xs = (0..30).map(|i| i as f32 / 30.0).collect::>(); 30 | //! 31 | //! let ys = xs 32 | //! .iter() 33 | //! .map(|x| Vec2::new(*x, 0.5 * x)) 34 | //! .collect::>(); 35 | //! 36 | //! plot.plot(ys); 37 | //! 38 | //! let plot_handle = plots.add(plot.clone()); 39 | //! commands.spawn().insert(plot_handle); 40 | //! } 41 | //! ``` 42 | //! 43 | //! 44 | //! For customizing the look of the curves and markers, see the [`Opt`] enum for the 45 | //! available options together with the [`Plot::plotopt`] and 46 | //! [`Plot::plotopt_func`] methods. For customizing the canvas (grid, colors, etc...), see the [`Plot`] fields. 47 | //! Setting the range of the x and y axes is done with the [`Plot::set_bounds`]`(lo, up)` method, but bevy_plot 48 | //! panics if `lo.x > up.x or lo.y > up.y`. 49 | //! 50 | //! Note that the library allows the user to 51 | //! * zoom in and out with the mousewheel, 52 | //! * move the origin with the mouse by pressing and dragging, 53 | //! * spawn a target and the corresponding coordinates by pressing the middle mouse button, and 54 | //! * change the Plot fields at runtime (see examples/runtime_setter.rs). 55 | 56 | mod plot; 57 | pub use plot::*; 58 | 59 | // mod bezier; 60 | mod canvas; 61 | mod inputs; 62 | // mod markers; 63 | // mod segments; 64 | mod util; 65 | -------------------------------------------------------------------------------- /src/main2.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | // mod inputs; 4 | pub mod util; 5 | // mod view; 6 | 7 | // use inputs::*; 8 | use util::*; 9 | // use view::*; 10 | 11 | use bevy::{ 12 | prelude::*, 13 | render::camera::OrthographicProjection, 14 | sprite::{Material2dPlugin, MaterialMesh2dBundle, Mesh2dHandle}, 15 | }; 16 | 17 | fn main() { 18 | let mut app = App::new(); 19 | 20 | app.insert_resource(WindowDescriptor { 21 | title: "I am a window!".to_string(), 22 | width: 3000., 23 | height: 1700., 24 | vsync: true, 25 | ..Default::default() 26 | }) 27 | // .insert_resource(Maps::default()) 28 | .insert_resource(Cursor::default()) 29 | .add_plugin(Material2dPlugin::::default()) 30 | .add_plugins(DefaultPlugins) 31 | // .add_asset::() 32 | .add_event::() 33 | .add_event::() 34 | .add_startup_system(main_setup) 35 | 36 | // .add_startup_system(view_setup) 37 | // .add_system(spawn_graph) 38 | // .add_system(change_shader) 39 | // .add_system(record_mouse_events_system) 40 | // .add_system(release_all) 41 | // .add_system(move_graph_control_point) 42 | // blah 43 | ; 44 | 45 | app.run(); 46 | } 47 | 48 | // fn test( 49 | // material: Res>, 50 | // ) 51 | 52 | fn main_setup( 53 | mut commands: Commands, 54 | mut spawn_graph_event: EventWriter, 55 | // mut my_shader_params: ResMut>, 56 | // mut maps: ResMut, 57 | // mut commands: Commands, 58 | mut meshes: ResMut>, 59 | mut materials: ResMut>, 60 | ) { 61 | let size = Vec2::new(300.0, 300.0); 62 | 63 | let mut shader_params = GraphEditShader { 64 | // color: Color::YELLOW, 65 | // clearcolor: Color::BLACK, 66 | bounds: Vec2::new(0.0, 1.0), 67 | vars: [Vec4::ZERO; 16], 68 | zoom_time_width: Vec3::new(1.0, 1.0, 1.0), 69 | hover_index: -1, 70 | size: Vec2::new(300.0, 300.0), 71 | }; 72 | 73 | shader_params.vars[0] = Vec4::new(0.2, 0.6, 0.0, 0.0); 74 | shader_params.vars[2] = Vec4::new(0.7, 0.8, 0.0, 0.0); 75 | shader_params.vars[1] = shader_params.vars[0] / 2.0 + shader_params.vars[2] / 2.0; 76 | 77 | // exponent of easing function 78 | shader_params.vars[1].z = 1.0; 79 | 80 | // linear version of the y position of the control point 81 | shader_params.vars[1].w = shader_params.vars[0].y / 2.0 + shader_params.vars[2].y / 2.0; 82 | 83 | // let shader_param_handle = my_shader_params.add(shader_params); 84 | 85 | let material = materials.add(shader_params); 86 | 87 | // quad 88 | commands.spawn().insert_bundle(MaterialMesh2dBundle { 89 | mesh: Mesh2dHandle(meshes.add(Mesh::from(shape::Quad::new(size)))), 90 | material: material.clone(), 91 | ..Default::default() 92 | }); 93 | 94 | commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 95 | commands.spawn_bundle(OrthographicCameraBundle { 96 | transform: Transform::from_translation(Vec3::new(00.0, 0.0, 10.0)) 97 | .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), 98 | orthographic_projection: OrthographicProjection { 99 | scale: 1.0, 100 | far: 100000.0, 101 | near: -100000.0, 102 | ..Default::default() 103 | }, 104 | ..OrthographicCameraBundle::new_2d() 105 | }); 106 | 107 | spawn_graph_event.send(SpawnGraphEvent { 108 | pos: Vec2::ZERO, 109 | shader_param_handle: material, 110 | id: 1112, 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /src/markers/markers.wgsl: -------------------------------------------------------------------------------- 1 | // // Import the standard 2d mesh uniforms and set their bind groups 2 | // #import bevy_sprite::mesh2d_view_bind_group 3 | 4 | // [[group(0), binding(0)]] 5 | // var view: View; 6 | 7 | 8 | // #import bevy_sprite::mesh2d_struct 9 | 10 | // [[group(2), binding(0)]] 11 | // var mesh: Mesh2d; 12 | 13 | #import bevy_sprite::mesh2d_bindings 14 | #import bevy_sprite::mesh2d_view_bindings 15 | 16 | 17 | 18 | // // The structure of the vertex buffer is as specified in `specialize()` 19 | // struct Vertex { 20 | // [[location(0)]] position: vec3; 21 | // [[location(1)]] normal: vec3; 22 | // [[location(2)]] uv: vec2; 23 | 24 | // // instanced 25 | // [[location(3)]] i_pos_scale: vec4; 26 | // [[location(4)]] i_color: vec4; 27 | // }; 28 | 29 | struct Vertex { 30 | @location(0) position: vec3, 31 | @location(1) normal: vec3, 32 | @location(2) uv: vec2, 33 | @location(3) i_pos_scale: vec4, 34 | @location(4) i_color: vec4, 35 | }; 36 | 37 | struct MarkerUniform { 38 | marker_size: f32, 39 | hole_size: f32, 40 | zoom: f32, 41 | point_type: i32, 42 | quad_size: f32, 43 | contour: f32, 44 | inner_canvas_size_in_pixels: vec2, 45 | canvas_position_in_pixels: vec2, 46 | color: vec4, 47 | marker_point_color: vec4, 48 | 49 | }; 50 | 51 | @group(1) @binding(0) 52 | var uni: MarkerUniform; 53 | 54 | // struct VertexOutput { 55 | // // The vertex shader must set the on-screen position of the vertex 56 | // [[builtin(position)]] clip_position: vec4; 57 | 58 | // [[location(0)]] uv: vec2; 59 | // [[location(1)]] pos_scale: vec4; 60 | // [[location(2)]] color: vec4; 61 | // }; 62 | 63 | struct VertexOutput { 64 | @builtin(position) clip_position: vec4, 65 | @location(0) uv: vec2, 66 | @location(1) pos_scale: vec4, 67 | @location(2) color: vec4, 68 | }; 69 | 70 | @vertex 71 | fn vertex(vertex: Vertex) -> VertexOutput { 72 | 73 | let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz ; 74 | let world_position = mesh.model * vec4(position, 1.0); 75 | 76 | var out: VertexOutput; 77 | 78 | out.clip_position = view.view_proj * world_position; 79 | out.color = vertex.i_color; 80 | out.uv = vertex.uv; 81 | out.pos_scale = vertex.i_pos_scale; 82 | 83 | return out; 84 | } 85 | 86 | fn fromLinear(linearRGB: vec4) -> vec4 { 87 | let cutoff: vec4 = vec4(linearRGB < vec4(0.0031308)); 88 | let higher: vec4 = vec4(1.055) * pow(linearRGB, vec4(1.0 / 2.4)) - vec4(0.055); 89 | let lower: vec4 = linearRGB * vec4(12.92); 90 | 91 | return mix(higher, lower, cutoff); 92 | } 93 | 94 | // Converts a color from sRGB gamma to linear light gamma 95 | fn toLinear(sRGB: vec4) -> vec4 { 96 | let cutoff = vec4(sRGB < vec4(0.04045)); 97 | let higher = pow((sRGB + vec4(0.055)) / vec4(1.055), vec4(2.4)); 98 | let lower = sRGB / vec4(12.92); 99 | 100 | return mix(higher, lower, cutoff); 101 | } 102 | 103 | 104 | // struct FragmentInput { 105 | // [[location(0)]] uv: vec2; 106 | // [[location(1)]] pos_scale: vec4; 107 | // [[location(2)]] color: vec4; 108 | // }; 109 | 110 | struct FragmentInput { 111 | @location(0) uv: vec2, 112 | @location(1) pos_scale: vec4, 113 | @location(2) color: vec4, 114 | }; 115 | 116 | 117 | fn cla(mi: f32, ma: f32, x: f32) -> f32 { 118 | if x < mi { 119 | return mi; 120 | } 121 | if x > ma { 122 | return ma; 123 | } 124 | return x; 125 | } 126 | 127 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 128 | let pa = p - a; 129 | let ba = b - a; 130 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.); 131 | return length(pa - ba * h); 132 | } 133 | 134 | fn sdRhombus(p: vec2, b: vec2) -> f32 { 135 | let q = abs(p); 136 | let qb = dot(q, vec2(b.x, -b.y)); 137 | let bb = dot(b, vec2(b.x, -b.y)); 138 | let h = clamp((-2. * qb + bb) / dot(b, b), -1., 1.); 139 | let d = length(q - 0.5 * b * vec2(1. - h, 1. + h)); 140 | return d * sign(q.x * b.y + q.y * b.x - b.x * b.y); 141 | } 142 | 143 | fn sdTriangleIsosceles(p: vec2, c: vec2) -> f32 { 144 | let q = vec2(abs(p.x), p.y); 145 | let a = q - c * clamp(dot(q, c) / dot(c, c), 0., 1.); 146 | let b = q - c * vec2(clamp(q.x / c.x, 0., 1.), 1.); 147 | let s = -sign(c.y); 148 | let d = min(vec2(dot(a, a), s * (q.x * c.y - q.y * c.x)), vec2(dot(b, b), s * (q.y - c.y))); 149 | return -sqrt(d.x) * sign(d.y); 150 | } 151 | 152 | fn sdStar(p: vec2, r: f32, n: u32, m: f32) -> f32 { 153 | let an = 3.141593 / f32(n); 154 | let en = 3.141593 / m; 155 | let acs = vec2(cos(an), sin(an)); 156 | let ecs = vec2(cos(en), sin(en)); 157 | let bn = (atan2(abs(p.x), p.y) % (2. * an)) - an; 158 | var q: vec2 = length(p) * vec2(cos(bn), abs(sin(bn))); 159 | q = q - r * acs; 160 | q = q + ecs * clamp(-dot(q, ecs), 0., r * acs.y / ecs.y); 161 | return length(q) * sign(q.x); 162 | } 163 | 164 | fn sdHeart(p: vec2) -> f32 { 165 | let q = vec2(abs(p.x), p.y); 166 | let w = q - vec2(0.25, 0.75); 167 | if q.x + q.y > 1.0 { return sqrt(dot(w, w)) - sqrt(2.) / 4.; } 168 | let u = q - vec2(0., 1.0); 169 | let v = q - 0.5 * max(q.x + q.y, 0.); 170 | return sqrt(min(dot(u, u), dot(v, v))) * sign(q.x - q.y); 171 | } 172 | 173 | fn sdMoon(p: vec2, d: f32, ra: f32, rb: f32) -> f32 { 174 | let q = vec2(p.x, abs(p.y)); 175 | let a = (ra * ra - rb * rb + d * d) / (2. * d); 176 | let b = sqrt(max(ra * ra - a * a, 0.)); 177 | if d * (q.x * b - q.y * a) > d * d * max(b - q.y, 0.) { return length(q - vec2(a, b)); } 178 | return max((length(q) - ra), -(length(q - vec2(d, 0.)) - rb)); 179 | } 180 | 181 | fn sdCross(p: vec2, b: vec2) -> f32 { 182 | var q: vec2 = abs(p); 183 | q = select(q.xy, q.yx, q.y > q.x); 184 | let t = q - b; 185 | let k = max(t.y, t.x); 186 | let w = select(vec2(b.y - q.x, -k), t, k > 0.); 187 | return sign(k) * length(max(w, vec2(0.))); 188 | } 189 | 190 | fn sdRoundedX(p: vec2, w: f32, r: f32) -> f32 { 191 | let q = abs(p); 192 | return length(q - min(q.x + q.y, w) * 0.5) - r; 193 | } 194 | 195 | fn sdCircle(p: vec2, c: vec2, r: f32) -> f32 { 196 | let d = length(p - c); 197 | return d - r; 198 | } 199 | 200 | 201 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 202 | var x = r.x; 203 | var y = r.y; 204 | x = select(r.z, r.x, p.x > 0.); 205 | y = select(r.w, r.y, p.x > 0.); 206 | x = select(y, x, p.y > 0.); 207 | let q = abs(p) - b + x; 208 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 209 | } 210 | 211 | fn sdBox(p: vec2, b: vec2) -> f32 { 212 | let d = (abs(p) - b) ; 213 | return length(max(d, vec2(0.))) + min(max(d.x, d.y), 0.); 214 | } 215 | 216 | 217 | 218 | 219 | 220 | @fragment 221 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 222 | 223 | let width = 0.041 ; 224 | let zoom = uni.zoom; 225 | 226 | var w = width * zoom ; 227 | var solid = width * zoom ; 228 | 229 | 230 | var out_col = uni.color; 231 | 232 | var uv = in.uv - vec2(0.5, 0.5); 233 | 234 | var uv_in_pixels = vec2(-uv.x, uv.y) * uni.quad_size - in.pos_scale.xy; 235 | 236 | let marker_size = uni.marker_size; 237 | 238 | let point_type = i32(uni.point_type); 239 | // let point_type = 6; 240 | 241 | // change the aliasing as a function of the zoom 242 | var circ_zoom = zoom; 243 | 244 | if zoom > .0 { 245 | circ_zoom = pow(zoom, 0.05); 246 | } 247 | 248 | if zoom < 1.0 { 249 | circ_zoom = sqrt(sqrt(zoom)); 250 | } 251 | 252 | // square -> 0 253 | // heart -> 1 254 | // rhombus -> 2 255 | // triangle -> 3 256 | // star -> 4 257 | // moon -> 5 258 | // cross -> 6 259 | // x -> 7 260 | // circle -> 8 261 | 262 | let black = vec4(0.0, 0.0, 0.0, 1.0); 263 | if point_type == -1 { 264 | return vec4(0.0); 265 | } else if point_type == 0 { // square -> 0 266 | 267 | let r = cla(0.01, 0.3, 0.2 * uni.marker_size); 268 | let side_size = cla(0.1, 0.45, 0.4 * uni.marker_size); 269 | 270 | let d = sdRoundedBox(uv, vec2(side_size, side_size), vec4(r, r, r, r)); 271 | let s = smoothstep(solid * 0.0, solid * 0.0 + w, d); 272 | 273 | out_col = out_col * (1.0 - s); 274 | 275 | 276 | // heart -> 1 277 | } else if point_type == 1 { 278 | uv.y = -uv.y; 279 | 280 | let heart_size = cla(0.2, 0.6, 0.15 * uni.marker_size); 281 | let w_heart = w / heart_size; 282 | 283 | let d = sdHeart((uv - vec2(0.0, -heart_size * 0.9 + 0.15)) / heart_size + vec2(0.0, 0.2)); 284 | 285 | let s = smoothstep(0.0, w_heart, d); 286 | 287 | out_col = out_col * (1.0 - s); 288 | 289 | 290 | // rhombus -> 2 291 | } else if point_type == 2 { 292 | 293 | let size = cla(0.1, 0.4, 0.3 * uni.marker_size); 294 | 295 | let d = sdRhombus(uv, vec2(size * 1.2, size * 0.8)); 296 | let s = smoothstep(0.0, w / circ_zoom, d); 297 | 298 | out_col = out_col * (1.0 - s); 299 | 300 | if uni.contour > 0.5 { 301 | let d = sdRhombus(uv, vec2(size * 1.2, size * 0.8) * 1.2); 302 | let s = smoothstep(0.0, w / circ_zoom, abs(d) - 0.02); 303 | 304 | out_col = mix(black, out_col, s); 305 | } 306 | 307 | 308 | // triangle -> 3 309 | } else if point_type == 3 { 310 | 311 | uv.y = -uv.y; 312 | 313 | let size = cla(0.13, 0.5, 0.3 * uni.marker_size); 314 | 315 | let d = sdTriangleIsosceles(uv - vec2(0.0, -size * 0.5), vec2(size * 0.7, size)); 316 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 317 | 318 | out_col = out_col * (1.0 - s); 319 | 320 | if uni.contour > 0.5 { 321 | let d = sdTriangleIsosceles(uv - vec2(0.0, -size * 0.5), vec2(size * 0.7, size)); 322 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 323 | 324 | out_col = mix(black, out_col, s); 325 | } 326 | 327 | // star -> 4 328 | } else if point_type == 4 { 329 | 330 | let star_size = cla(0.05, 0.2, 0.1 * uni.marker_size); 331 | 332 | let d = sdStar(uv, star_size, u32(5), 0.35); 333 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 334 | 335 | out_col = out_col * (1.0 - s); 336 | 337 | // let sb = smoothstep(1.0 , 0.0 + w / circ_zoom, d ); 338 | 339 | if uni.contour > 0.5 { 340 | let d = sdStar(uv, star_size, u32(5), 0.35); 341 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 342 | out_col = mix(black, out_col, s); 343 | } 344 | 345 | // moon -> 5 346 | } else if point_type == 5 { 347 | 348 | let moon_size = cla(0.3, 1.3, uni.marker_size); 349 | 350 | let d = sdMoon(uv - vec2(0.05 * (1.0 + moon_size * 0.7), 0.0), 0.3 * moon_size, 0.35 * moon_size, 0.35 * moon_size); 351 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 352 | 353 | out_col = out_col * (1.0 - s); 354 | 355 | if uni.contour > 0.5 { 356 | let d = sdMoon(uv - vec2(0.05 * (1.0 + moon_size * 0.7), 0.0), 0.3 * moon_size, 0.35 * moon_size, 0.35 * moon_size); 357 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 358 | out_col = mix(black, out_col, s); 359 | } 360 | 361 | // cross -> 6 362 | } else if point_type == 6 { 363 | 364 | let cross_size = cla(0.1, 0.4, 0.25 * uni.marker_size); 365 | 366 | let d = sdCross(uv, vec2(cross_size, cross_size / 3.0)); 367 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, d); 368 | 369 | 370 | out_col = out_col * (1.0 - s); 371 | 372 | if uni.contour > 0.5 { 373 | let d = sdCross(uv, vec2(cross_size, cross_size / 3.0)); 374 | let s = smoothstep(0.0, 0.0 + w / circ_zoom, abs(d) - 0.02); 375 | out_col = mix(black, out_col, s); 376 | } 377 | 378 | 379 | // x -> 7 380 | } else if point_type == 7 { 381 | let ex_size = cla(0.15, 0.6, 0.3 * uni.marker_size); 382 | 383 | let start_size = 0.1; 384 | let d = sdRoundedX(uv, ex_size, ex_size / 6.0); 385 | let s = smoothstep(0.0, w / circ_zoom, d); 386 | 387 | out_col = out_col * (1.0 - s); 388 | 389 | if uni.contour > 0.5 { 390 | let d = sdRoundedX(uv, ex_size, ex_size / 6.0); 391 | let s = smoothstep(0.0, w / circ_zoom, abs(d) - 0.02); 392 | out_col = mix(black, out_col, s); 393 | } 394 | 395 | // circles -> 8 396 | } else if point_type == 8 { 397 | 398 | let circle_size = cla(0.04, 0.45, 0.25 * uni.marker_size); 399 | 400 | let r = circle_size; 401 | let d = sdCircle(uv, vec2(0.0, 0.0), circle_size); 402 | let s = smoothstep(0.0, w, d); 403 | 404 | out_col = out_col * (1.0 - s) ; 405 | 406 | if uni.contour > 0.5 { 407 | let d = sdCircle(uv, vec2(0.0, 0.0), circle_size); 408 | let s = smoothstep(0.0, w, abs(d) - 0.02); 409 | out_col = mix(black, out_col, s); 410 | } 411 | } 412 | 413 | // tiny circle at exact location of data point 414 | let inner_circle_color = uni.marker_point_color; 415 | let dc = sdCircle(uv, vec2(0.0, 0.0), 0.025 * uni.hole_size); 416 | let sc = smoothstep(0.0, w / circ_zoom * uni.hole_size, dc); 417 | out_col = mix(out_col, inner_circle_color, 1.0 - sc) ; 418 | 419 | // mask with the canvas 420 | let r = 0.02 * uni.inner_canvas_size_in_pixels.x; 421 | let d = sdRoundedBox( 422 | uv_in_pixels + uni.canvas_position_in_pixels, 423 | uni.inner_canvas_size_in_pixels / 2.0 - 1.0, 424 | vec4(r, r, r, r) 425 | ); 426 | 427 | let s = smoothstep(0.0, 0.1, d); 428 | out_col = mix(out_col, vec4(0.0, 0.3, 0.3, 0.0), s) ; 429 | 430 | return out_col; 431 | } -------------------------------------------------------------------------------- /src/markers/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | // asset::Assets, 3 | core_pipeline::core_2d::Transparent2d, 4 | ecs::system::lifetimeless::{Read, SQuery, SRes}, 5 | ecs::system::SystemParamItem, 6 | prelude::*, 7 | reflect::TypeUuid, 8 | render::{ 9 | extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, 10 | extract_component::{ExtractComponent, ExtractComponentPlugin}, 11 | extract_resource::ExtractResource, 12 | mesh::{GpuBufferInfo, MeshVertexBufferLayout}, 13 | render_asset::RenderAssets, 14 | render_phase::{ 15 | AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, 16 | TrackedRenderPass, 17 | }, 18 | // render_resource::{std140::AsStd140, *}, 19 | render_resource::*, 20 | renderer::RenderDevice, 21 | 22 | view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, 23 | view::{ExtractedView, NoFrustumCulling}, 24 | RenderApp, 25 | RenderStage, 26 | }, 27 | sprite::{ 28 | Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, SetMesh2dBindGroup, SetMesh2dViewBindGroup, 29 | }, 30 | utils::FloatOrd, 31 | }; 32 | 33 | use crate::plot::*; 34 | use crate::util::*; 35 | use bytemuck::{Pod, Zeroable}; 36 | // use crevice::std140::AsStd140; 37 | 38 | // TODOs: 39 | // 1) Modify the transform instead of spawning brand new entities 40 | // this way, the uniform will stay the same 41 | 42 | pub(crate) fn markers_setup( 43 | mut commands: Commands, 44 | mut meshes: ResMut>, 45 | mut change_canvas_material_event: EventReader, 46 | mut plots: ResMut>, 47 | query: Query<(Entity, &Handle), With>, 48 | ) { 49 | for event in change_canvas_material_event.iter() { 50 | // 51 | for (entity, plot_handle) in query.iter() { 52 | if event.plot_handle == *plot_handle { 53 | commands.entity(entity).despawn(); 54 | } 55 | } 56 | 57 | let mut plot = plots.get_mut(&event.plot_handle).unwrap(); 58 | 59 | plot_points( 60 | &mut commands, 61 | &mut meshes, 62 | // ys, 63 | &mut plot, 64 | &event.plot_handle, 65 | ) 66 | } 67 | } 68 | 69 | fn plot_points( 70 | commands: &mut Commands, 71 | meshes: &mut ResMut>, 72 | plot: &mut Plot, 73 | plot_handle: &Handle, 74 | ) { 75 | let data = plot.data.clone(); 76 | // let color = data.marker_plot.color; 77 | for marker_plot in data.marker_groups.iter() { 78 | let ys = marker_plot.data.clone(); 79 | // let color = marker_plot.color; 80 | // let ys_world = plot.plot_to_local(&ys); 81 | let ys_world = ys.iter().map(|y| plot.to_local(*y)).collect::>(); 82 | 83 | let quad_size = 30.0; 84 | 85 | commands 86 | .spawn_bundle(( 87 | Mesh2dHandle(meshes.add(Mesh::from(shape::Quad { 88 | size: Vec2::splat(quad_size), 89 | flip: false, 90 | }))), 91 | GlobalTransform::default(), 92 | Transform::from_translation(Vec3::new(0.0, 0.0, 1.12)), 93 | Visibility::default(), 94 | ComputedVisibility::default(), 95 | MarkerInstanceMatData( 96 | ys_world 97 | .iter() 98 | .map(|v| MarkerInstanceData { 99 | // 100 | // TODO: take inner border into account 101 | // 102 | position: Vec3::new(v.x, v.y, 1.01) + plot.canvas_position.extend(0.000), 103 | scale: 1.0, 104 | color: Color::rgba(0.8, 0.6, 0.1, 1.0).as_rgba_f32(), 105 | }) 106 | .collect(), 107 | ), 108 | NoFrustumCulling, 109 | // NoFrustumCulling, 110 | )) 111 | .insert(plot_handle.clone()) 112 | .insert(MarkerUniform { 113 | marker_size: marker_plot.size, 114 | hole_size: 1.0, 115 | zoom: 1.0, 116 | marker_type: marker_plot.marker_style.to_int32(), 117 | marker_point_color: col_to_vec4(marker_plot.marker_point_color), 118 | color: col_to_vec4(marker_plot.color), 119 | quad_size, 120 | inner_canvas_size_in_pixels: plot.canvas_size / (1.0 + plot.outer_border), 121 | // outer_border: plot.outer_border, 122 | canvas_position: plot.canvas_position, 123 | contour: if marker_plot.draw_contour { 1.0 } else { 0.0 }, 124 | }); 125 | } 126 | } 127 | 128 | #[derive(Component)] 129 | pub(crate) struct MarkerInstanceMatData(Vec); 130 | 131 | impl ExtractComponent for MarkerInstanceMatData { 132 | type Query = &'static MarkerInstanceMatData; 133 | type Filter = (); 134 | 135 | fn extract_component(item: bevy::ecs::query::QueryItem) -> Self { 136 | MarkerInstanceMatData(item.0.clone()) 137 | } 138 | } 139 | 140 | #[derive(Component, Default)] 141 | pub(crate) struct MarkerMesh2d; 142 | 143 | // /// Uniform sent to markers.wgsl 144 | // #[derive(Component, Clone, AsStd140, ExtractResource)] 145 | // pub(crate) struct MarkerUniform { 146 | // pub marker_size: f32, 147 | // /// When the ```marker_point_color``` field is different from the ```color``` field, 148 | // /// there is a small visible circle within the marker. ```hole_size``` controls the size of the circle. 149 | // pub hole_size: f32, 150 | // pub zoom: f32, 151 | // pub marker_type: i32, 152 | // /// Size of the instanced square quad for one marker. 153 | // pub quad_size: f32, 154 | 155 | // /// Shows a black contour around the marker if the value is > 0.5. 156 | // pub contour: f32, 157 | // pub inner_canvas_size_in_pixels: crevice::std140::Vec2, 158 | // pub canvas_position: crevice::std140::Vec2, 159 | // pub color: crevice::std140::Vec4, 160 | 161 | // /// Color of the small circle within the marker. 162 | // pub marker_point_color: crevice::std140::Vec4, 163 | // } 164 | 165 | #[derive(Component, Clone, AsBindGroup, ShaderType)] 166 | pub(crate) struct MarkerUniform { 167 | #[uniform(0)] 168 | pub marker_size: f32, 169 | /// When the ```marker_point_color``` field is different from the ```color``` field, 170 | /// there is a small visible circle within the marker. ```hole_size``` controls the size of the circle. 171 | #[uniform(0)] 172 | pub hole_size: f32, 173 | #[uniform(0)] 174 | pub zoom: f32, 175 | #[uniform(0)] 176 | pub marker_type: i32, 177 | /// Size of the instanced square quad for one marker. 178 | #[uniform(0)] 179 | pub quad_size: f32, 180 | 181 | /// Shows a black contour around the marker if the value is > 0.5. 182 | #[uniform(0)] 183 | pub contour: f32, 184 | #[uniform(0)] 185 | pub inner_canvas_size_in_pixels: Vec2, 186 | #[uniform(0)] 187 | pub canvas_position: Vec2, 188 | #[uniform(0)] 189 | pub color: Vec4, 190 | 191 | /// Color of the small circle within the marker. 192 | #[uniform(0)] 193 | pub marker_point_color: Vec4, 194 | } 195 | 196 | impl ExtractComponent for MarkerUniform { 197 | type Query = &'static MarkerUniform; 198 | type Filter = (); 199 | 200 | fn extract_component(item: bevy::ecs::query::QueryItem) -> Self { 201 | item.clone() 202 | 203 | // MarkerUniform { 204 | // marker_size: item.marker_size, 205 | // hole_size: item.hole_size, 206 | // zoom: item.0.zoom, 207 | // marker_type: item.0.marker_type, 208 | // quad_size: item.0.quad_size, 209 | // contour: item.0.contour, 210 | // inner_canvas_size_in_pixels: item.0.inner_canvas_size_in_pixels, 211 | // canvas_position: item.0.canvas_position, 212 | // color: item.0.color, 213 | // marker_point_color: item.0.marker_point_color, 214 | // } 215 | } 216 | } 217 | 218 | // TODO: we have instance data, but we don't use it at the moment. 219 | // One use case would be to have marker size as an additional dimension. 220 | 221 | #[derive(Clone, Copy, Pod, Zeroable)] 222 | #[repr(C)] 223 | struct MarkerInstanceData { 224 | position: Vec3, 225 | scale: f32, 226 | color: [f32; 4], 227 | } 228 | 229 | /// Custom pipeline for 2d meshes with vertex colors 230 | pub(crate) struct MarkerMesh2dPipeline { 231 | /// this pipeline wraps the standard [`Mesh2dPipeline`] 232 | mesh2d_pipeline: Mesh2dPipeline, 233 | pub custom_uniform_layout: BindGroupLayout, 234 | // pub shader: Handle, 235 | // material_layout: BindGroupLayout, 236 | } 237 | 238 | impl FromWorld for MarkerMesh2dPipeline { 239 | fn from_world(world: &mut World) -> Self { 240 | let mesh2d_pipeline = Mesh2dPipeline::from_world(world).clone(); 241 | 242 | let render_device = world.get_resource::().unwrap(); 243 | 244 | let custom_uniform_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { 245 | entries: &[BindGroupLayoutEntry { 246 | binding: 0, 247 | visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, 248 | ty: BindingType::Buffer { 249 | ty: BufferBindingType::Uniform, 250 | has_dynamic_offset: true, 251 | min_binding_size: BufferSize::new(MarkerUniform::min_size().into()), 252 | }, 253 | count: None, 254 | }], 255 | label: Some("markers_uniform_layout"), 256 | }); 257 | 258 | // let world = world.cell(); 259 | // let asset_server = world.get_resource::().unwrap(); 260 | 261 | // let shader = asset_server.load("../assets/shaders/markers.wgsl"); 262 | 263 | // let _result = asset_server.watch_for_changes(); 264 | 265 | Self { 266 | mesh2d_pipeline, 267 | custom_uniform_layout, 268 | // shader, 269 | } 270 | } 271 | } 272 | 273 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 274 | pub(crate) struct MarkerPipelineKey { 275 | mesh: Mesh2dPipelineKey, 276 | shader_handle: Handle, 277 | } 278 | 279 | impl SpecializedMeshPipeline for MarkerMesh2dPipeline { 280 | type Key = MarkerPipelineKey; 281 | 282 | fn specialize( 283 | &self, 284 | key: Self::Key, 285 | layout: &MeshVertexBufferLayout, 286 | ) -> Result { 287 | let mut descriptor = self.mesh2d_pipeline.specialize(key.mesh, layout)?; 288 | 289 | descriptor.vertex.shader = key.shader_handle.clone(); 290 | descriptor.vertex.buffers.push(VertexBufferLayout { 291 | array_stride: std::mem::size_of::() as u64, 292 | step_mode: VertexStepMode::Instance, 293 | attributes: vec![ 294 | VertexAttribute { 295 | format: VertexFormat::Float32x4, 296 | offset: 0, 297 | shader_location: 3, // shader locations 0-2 are taken up by Position, Normal and UV attributes 298 | }, 299 | VertexAttribute { 300 | format: VertexFormat::Float32x4, 301 | offset: VertexFormat::Float32x4.size(), 302 | shader_location: 4, 303 | }, 304 | // VertexAttribute { 305 | // format: VertexFormat::Float32x4, 306 | // offset: VertexFormat::Float32x4.size(), 307 | // shader_location: 5, 308 | // }, 309 | ], 310 | }); 311 | descriptor.fragment.as_mut().unwrap().shader = key.shader_handle.clone(); 312 | descriptor.layout = Some(vec![ 313 | self.mesh2d_pipeline.view_layout.clone(), 314 | self.custom_uniform_layout.clone(), 315 | self.mesh2d_pipeline.mesh_layout.clone(), 316 | ]); 317 | 318 | Ok(descriptor) 319 | } 320 | } 321 | 322 | // This specifies how to render a colored 2d mesh 323 | type DrawMarkerMesh2d = ( 324 | // Set the pipeline 325 | SetItemPipeline, 326 | // Set the view uniform as bind group 0 327 | SetMesh2dViewBindGroup<0>, 328 | // Set the marker uniform as bind group 1 329 | SetMarkerUniformBindGroup<1>, 330 | // Set the mesh uniform as bind group 2 331 | SetMesh2dBindGroup<2>, 332 | // Draw the mesh 333 | DrawMarkerMeshInstanced, 334 | ); 335 | 336 | pub(crate) struct MarkerMesh2dPlugin; 337 | 338 | pub(crate) struct MarkerShaderHandle(pub Handle); 339 | 340 | pub const MARKER_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9826352034109932589); 341 | 342 | impl Plugin for MarkerMesh2dPlugin { 343 | fn build(&self, app: &mut App) { 344 | let mut shaders = app.world.get_resource_mut::>().unwrap(); 345 | 346 | let handle_untyped = MARKER_SHADER_HANDLE.clone(); 347 | 348 | shaders.set_untracked(handle_untyped.clone(), Shader::from_wgsl(include_str!("markers.wgsl"))); 349 | 350 | let shader_typed_handle = shaders.get_handle(handle_untyped); 351 | 352 | // app.add_plugin(UniformComponentPlugin::::default()); 353 | app.add_plugin(ExtractComponentPlugin::::default()); 354 | app.add_plugin(UniformComponentPlugin::::default()); 355 | app.add_plugin(ExtractComponentPlugin::::default()); 356 | 357 | // Register our custom draw function and pipeline, and add our render systems 358 | let render_app = app.get_sub_app_mut(RenderApp).unwrap(); 359 | render_app 360 | .add_render_command::() 361 | .init_resource::() 362 | .init_resource::>() 363 | .insert_resource(MarkerShaderHandle(shader_typed_handle)) 364 | .add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers) 365 | // .add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d) 366 | .add_system_to_stage(RenderStage::Queue, queue_marker_uniform_bind_group) 367 | .add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d); 368 | } 369 | } 370 | 371 | #[allow(clippy::too_many_arguments)] 372 | fn queue_colored_mesh2d( 373 | transparent_draw_functions: Res>, 374 | colored_mesh2d_pipeline: Res, 375 | mut pipelines: ResMut>, 376 | mut pipeline_cache: ResMut, 377 | msaa: Res, 378 | render_meshes: Res>, 379 | shader_handle: Res, 380 | colored_mesh2d: Query<(Entity, &Mesh2dHandle, &Mesh2dUniform), With>, 381 | mut views: Query<(&ExtractedView, &mut RenderPhase)>, 382 | ) { 383 | if colored_mesh2d.is_empty() { 384 | return; 385 | } 386 | 387 | // Iterate each view (a camera is a view) 388 | // for (visible_entities, mut transparent_phase) in views.iter_mut() { 389 | for (_view, mut transparent_phase) in views.iter_mut() { 390 | let draw_colored_mesh2d = transparent_draw_functions.read().get_id::().unwrap(); 391 | 392 | // let draw_colored_mesh2d = transparent_draw_functions.read().id::(); 393 | 394 | // let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); 395 | 396 | let mesh_key = MarkerPipelineKey { 397 | mesh: Mesh2dPipelineKey::from_msaa_samples(msaa.samples), 398 | shader_handle: shader_handle.0.clone(), 399 | }; 400 | 401 | // let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); 402 | 403 | // Queue all entities visible to that view 404 | // for visible_entity in &visible_entities.entities { 405 | for (entity, mesh2d_handle, mesh2d_uniform) in colored_mesh2d.iter() { 406 | // if let Ok((mesh2d_handle, mesh2d_uniform)) = colored_mesh2d.get(*visible_entity) { 407 | let mut mesh2d_key = mesh_key.clone(); 408 | if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { 409 | mesh2d_key.mesh |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); 410 | 411 | if let Ok(pipeline_id) = pipelines.specialize( 412 | &mut pipeline_cache, 413 | &colored_mesh2d_pipeline, 414 | mesh2d_key, 415 | &mesh.layout.clone(), 416 | ) { 417 | let mesh_z = mesh2d_uniform.transform.w_axis.z; 418 | transparent_phase.add(Transparent2d { 419 | entity, 420 | draw_function: draw_colored_mesh2d, 421 | pipeline: pipeline_id, 422 | sort_key: FloatOrd(mesh_z), 423 | batch_range: None, 424 | }); 425 | } 426 | } 427 | // } 428 | } 429 | } 430 | } 431 | 432 | fn prepare_instance_buffers( 433 | mut commands: Commands, 434 | query: Query<(Entity, &MarkerInstanceMatData)>, 435 | render_device: Res, 436 | ) { 437 | for (entity, instance_data) in query.iter() { 438 | let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { 439 | label: Some("marker instance data buffer"), 440 | contents: bytemuck::cast_slice(instance_data.0.as_slice()), 441 | usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, 442 | }); 443 | commands.entity(entity).insert(MarkerInstanceBuffer { 444 | buffer, 445 | length: instance_data.0.len(), 446 | }); 447 | } 448 | } 449 | 450 | struct MarkerUniformBindGroup { 451 | pub value: BindGroup, 452 | } 453 | 454 | fn queue_marker_uniform_bind_group( 455 | mut commands: Commands, 456 | mesh2d_pipeline: Res, 457 | render_device: Res, 458 | // mesh2d_uniforms: Res, 459 | mesh2d_uniforms: Res>, 460 | ) { 461 | if let Some(binding) = mesh2d_uniforms.uniforms().binding() { 462 | commands.insert_resource(MarkerUniformBindGroup { 463 | value: render_device.create_bind_group(&BindGroupDescriptor { 464 | entries: &[BindGroupEntry { 465 | binding: 0, 466 | resource: binding, 467 | }], 468 | label: Some("MarkersUniform_bind_group"), 469 | layout: &mesh2d_pipeline.custom_uniform_layout, 470 | }), 471 | }); 472 | } 473 | } 474 | 475 | struct SetMarkerUniformBindGroup; 476 | impl EntityRenderCommand for SetMarkerUniformBindGroup { 477 | type Param = ( 478 | SRes, 479 | SQuery>>, 480 | ); 481 | #[inline] 482 | fn render<'w>( 483 | _view: Entity, 484 | item: Entity, 485 | (mesh2d_bind_group, mesh2d_query): SystemParamItem<'w, '_, Self::Param>, 486 | pass: &mut TrackedRenderPass<'w>, 487 | ) -> RenderCommandResult { 488 | let mesh2d_index = mesh2d_query.get(item).unwrap(); 489 | 490 | pass.set_bind_group(I, &mesh2d_bind_group.into_inner().value, &[mesh2d_index.index()]); 491 | RenderCommandResult::Success 492 | } 493 | } 494 | 495 | #[derive(Component)] 496 | struct MarkerInstanceBuffer { 497 | buffer: Buffer, 498 | length: usize, 499 | } 500 | 501 | struct DrawMarkerMeshInstanced; 502 | impl EntityRenderCommand for DrawMarkerMeshInstanced { 503 | type Param = ( 504 | SRes>, 505 | SQuery>, 506 | SQuery>, 507 | ); 508 | 509 | // type Param = SRes>; 510 | // type ViewWorldQuery = (); 511 | // type ItemWorldQuery = (Read>, Read); 512 | 513 | #[inline] 514 | fn render<'w>( 515 | _view: Entity, 516 | item: Entity, 517 | (meshes, mesh_query, instance_buffer_query): SystemParamItem<'w, '_, Self::Param>, 518 | pass: &mut TrackedRenderPass<'w>, 519 | ) -> RenderCommandResult { 520 | let mesh_handle = &mesh_query.get(item).unwrap().0; 521 | let instance_buffer = instance_buffer_query.get_inner(item).unwrap(); 522 | 523 | let gpu_mesh = match meshes.into_inner().get(mesh_handle) { 524 | Some(gpu_mesh) => gpu_mesh, 525 | None => return RenderCommandResult::Failure, 526 | }; 527 | 528 | pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); 529 | pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..)); 530 | 531 | match &gpu_mesh.buffer_info { 532 | GpuBufferInfo::Indexed { 533 | buffer, 534 | index_format, 535 | count, 536 | } => { 537 | pass.set_index_buffer(buffer.slice(..), 0, *index_format); 538 | pass.draw_indexed(0..*count, 0, 0..instance_buffer.length as u32); 539 | } 540 | GpuBufferInfo::NonIndexed { vertex_count } => { 541 | pass.draw(0..*vertex_count, 0..instance_buffer.length as u32); 542 | } 543 | } 544 | RenderCommandResult::Success 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/plot/colors.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[allow(dead_code)] 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 6 | pub enum PlotColor { 7 | Gray, 8 | Black, 9 | LightPink, 10 | Pink, 11 | Violet, 12 | Blue, 13 | Green, 14 | Salmon, 15 | Orange, 16 | Latte, 17 | Cream, 18 | Yellow, 19 | } 20 | 21 | #[derive(Resource)] 22 | pub struct ColorPalette { 23 | pub colors: HashMap>, 24 | } 25 | 26 | impl Default for ColorPalette { 27 | fn default() -> Self { 28 | Self { 29 | colors: make_color_palette(), 30 | } 31 | } 32 | } 33 | 34 | /// To get a particular color, get the color from the hashmap with a key of the PlotColor enum. 35 | /// Then get the shade of this color from the Vec of colors, the higher the index the darker the shade. 36 | pub fn make_color_palette() -> HashMap> { 37 | let gray = vec!["d4d2dd", "b4b3b9", "aaa9b1", "9f9ea4", "66656a", "59585e"] 38 | .iter() 39 | .map( 40 | // to hex 41 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 42 | ) 43 | .collect::>(); 44 | 45 | let black = vec!["38373c", "323337", "49484d", "323136", "1c1c1c", "111111"] 46 | .iter() 47 | .map( 48 | // to hex 49 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 50 | ) 51 | .collect::>(); 52 | 53 | let light_pink = vec!["f1b8bf", "d08693", "ecbbbf", "f2b9bf", "febdc5", "df9ea6"] 54 | .iter() 55 | .map( 56 | // to hex 57 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 58 | ) 59 | .collect::>(); 60 | 61 | let pink = vec!["f05285", "f9558a", "e74479", "f85187", "e9467d", "ca1950"] 62 | .iter() 63 | .map( 64 | // to hex 65 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 66 | ) 67 | .collect::>(); 68 | 69 | let violet = vec!["9e6ea2", "94639a", "64356c", "9d71a2", "714576", "4b2451"] 70 | .iter() 71 | .map( 72 | // to hex 73 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 74 | ) 75 | .collect::>(); 76 | 77 | let blue = vec!["5197ca", "4a8dc1", "4285ba", "226599", "3b6d90", "1c567e"] 78 | .iter() 79 | .map( 80 | // to hex 81 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 82 | ) 83 | .collect::>(); 84 | 85 | let green = vec!["afce92", "a2c986", "b6dd9a", "8eb274", "8eb274", "366821"] 86 | .iter() 87 | .map( 88 | // to hex 89 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 90 | ) 91 | .collect::>(); 92 | 93 | let salmon = vec!["f96960", "e6564d", "fc655e", "df4442", "dc4846", "bb2727"] 94 | .iter() 95 | .map( 96 | // to hex 97 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 98 | ) 99 | .collect::>(); 100 | 101 | let orange = vec!["f8ae6d", "ffaf6a", "e78347", "f28e50", "e16f3b", "cb6229"] 102 | .iter() 103 | .map( 104 | // to hex 105 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 106 | ) 107 | .collect::>(); 108 | 109 | let latte = vec!["dbb993", "e5c49b", "dbbb92", "d1ae86", "be9b71", "b38e62"] 110 | .iter() 111 | .map( 112 | // to hex 113 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 114 | ) 115 | .collect::>(); 116 | 117 | let cream = vec!["f7efe4", "f6edde", "f5e9d9", "f2e6d8", "e9dbce", "e8dccc"] 118 | .iter() 119 | .map( 120 | // to hex 121 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 122 | ) 123 | .collect::>(); 124 | 125 | let yellow = vec!["fcd402", "fcd305", "fad008", "efc000", "f9c907", "d8a600"] 126 | .iter() 127 | .map( 128 | // to hex 129 | |h| Color::Srgba(Srgba::hex(h).unwrap()), 130 | ) 131 | .collect::>(); 132 | 133 | let mut colors = HashMap::new(); 134 | 135 | colors.insert(PlotColor::Gray, gray); 136 | colors.insert(PlotColor::Black, black); 137 | colors.insert(PlotColor::LightPink, light_pink); 138 | colors.insert(PlotColor::Pink, pink); 139 | colors.insert(PlotColor::Violet, violet); 140 | colors.insert(PlotColor::Blue, blue); 141 | colors.insert(PlotColor::Green, green); 142 | colors.insert(PlotColor::Salmon, salmon); 143 | colors.insert(PlotColor::Orange, orange); 144 | colors.insert(PlotColor::Latte, latte); 145 | colors.insert(PlotColor::Cream, cream); 146 | colors.insert(PlotColor::Yellow, yellow); 147 | 148 | colors 149 | } 150 | -------------------------------------------------------------------------------- /src/plot/mod.rs: -------------------------------------------------------------------------------- 1 | mod colors; 2 | pub mod plot; 3 | pub mod plot_format; 4 | 5 | pub use colors::*; 6 | pub use plot::*; 7 | pub use plot_format::*; 8 | -------------------------------------------------------------------------------- /src/plot/plot_format.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct PlotFormat { 5 | pub data: Vec, 6 | } 7 | 8 | pub trait Plotable { 9 | fn into_plot_format(&self) -> PlotFormat; 10 | } 11 | 12 | impl Plotable for Vec { 13 | fn into_plot_format(&self) -> PlotFormat { 14 | PlotFormat { data: self.clone() } 15 | } 16 | } 17 | 18 | impl Plotable for Vec<(f64, f64)> { 19 | fn into_plot_format(&self) -> PlotFormat { 20 | PlotFormat { 21 | data: self 22 | .iter() 23 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 24 | .collect(), 25 | } 26 | } 27 | } 28 | 29 | impl Plotable for Vec<(f32, f32)> { 30 | fn into_plot_format(&self) -> PlotFormat { 31 | PlotFormat { 32 | data: self 33 | .iter() 34 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 35 | .collect(), 36 | } 37 | } 38 | } 39 | 40 | impl Plotable for Vec<(i32, i32)> { 41 | fn into_plot_format(&self) -> PlotFormat { 42 | PlotFormat { 43 | data: self 44 | .iter() 45 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 46 | .collect(), 47 | } 48 | } 49 | } 50 | 51 | impl Plotable for Vec<(i64, i64)> { 52 | fn into_plot_format(&self) -> PlotFormat { 53 | PlotFormat { 54 | data: self 55 | .iter() 56 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 57 | .collect(), 58 | } 59 | } 60 | } 61 | 62 | impl Plotable for Vec<(i16, i16)> { 63 | fn into_plot_format(&self) -> PlotFormat { 64 | PlotFormat { 65 | data: self 66 | .iter() 67 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 68 | .collect(), 69 | } 70 | } 71 | } 72 | 73 | impl Plotable for Vec<(i8, i8)> { 74 | fn into_plot_format(&self) -> PlotFormat { 75 | PlotFormat { 76 | data: self 77 | .iter() 78 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 79 | .collect(), 80 | } 81 | } 82 | } 83 | 84 | impl Plotable for Vec<(u8, u8)> { 85 | fn into_plot_format(&self) -> PlotFormat { 86 | PlotFormat { 87 | data: self 88 | .iter() 89 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 90 | .collect(), 91 | } 92 | } 93 | } 94 | 95 | impl Plotable for Vec<(u16, u16)> { 96 | fn into_plot_format(&self) -> PlotFormat { 97 | PlotFormat { 98 | data: self 99 | .iter() 100 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 101 | .collect(), 102 | } 103 | } 104 | } 105 | 106 | impl Plotable for Vec<(u32, u32)> { 107 | fn into_plot_format(&self) -> PlotFormat { 108 | PlotFormat { 109 | data: self 110 | .iter() 111 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 112 | .collect(), 113 | } 114 | } 115 | } 116 | 117 | impl Plotable for Vec<(u64, u64)> { 118 | fn into_plot_format(&self) -> PlotFormat { 119 | PlotFormat { 120 | data: self 121 | .iter() 122 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 123 | .collect(), 124 | } 125 | } 126 | } 127 | 128 | impl Plotable for Vec<(usize, usize)> { 129 | fn into_plot_format(&self) -> PlotFormat { 130 | PlotFormat { 131 | data: self 132 | .iter() 133 | .map(|(x, y)| Vec2::new(*x as f32, *y as f32)) 134 | .collect(), 135 | } 136 | } 137 | } 138 | 139 | impl Plotable for Vec { 140 | fn into_plot_format(&self) -> PlotFormat { 141 | PlotFormat { 142 | data: self 143 | .iter() 144 | .enumerate() 145 | .map(|(i, x)| Vec2::new(i as f32, *x)) 146 | .collect(), 147 | } 148 | } 149 | } 150 | 151 | impl Plotable for Vec { 152 | fn into_plot_format(&self) -> PlotFormat { 153 | PlotFormat { 154 | data: self 155 | .iter() 156 | .enumerate() 157 | .map(|(i, x)| Vec2::new(i as f32, *x as f32)) 158 | .collect(), 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/segments/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod segments; 2 | #[allow(unused_imports)] 3 | pub use segments::*; 4 | -------------------------------------------------------------------------------- /src/segments/segments.rs: -------------------------------------------------------------------------------- 1 | use crate::plot::*; 2 | use crate::util::*; 3 | use bevy::{ 4 | asset::Assets, 5 | prelude::*, 6 | reflect::TypeUuid, 7 | render::{ 8 | mesh::{Indices, MeshVertexAttribute, MeshVertexBufferLayout}, 9 | render_resource::*, 10 | renderer::RenderDevice, 11 | {texture::BevyDefault, texture::GpuImage}, 12 | }, 13 | sprite::{ 14 | Material2dKey, Material2dPlugin, MaterialMesh2dBundle, Mesh2dHandle, Mesh2dPipeline, 15 | Mesh2dPipelineKey, 16 | }, 17 | }; 18 | 19 | // TODO: circular ends in mesh and/or linear joints 20 | 21 | pub(crate) fn segments_setup( 22 | mut commands: Commands, 23 | mut meshes: ResMut>, 24 | mut change_canvas_material_event: EventReader, 25 | mut plots: ResMut>, 26 | mut segment_material: ResMut>, 27 | query: Query<(Entity, &Handle), With>>, 28 | ) { 29 | for event in change_canvas_material_event.iter() { 30 | // 31 | for (entity, plot_handle) in query.iter() { 32 | if event.plot_handle == *plot_handle { 33 | commands.entity(entity).despawn(); 34 | } 35 | } 36 | 37 | if let Some(mut plot) = plots.get_mut(&event.plot_handle) { 38 | plot_segments( 39 | &mut commands, 40 | &mut meshes, 41 | &mut segment_material, 42 | &mut plot, 43 | &event.plot_handle, 44 | ) 45 | } 46 | } 47 | } 48 | 49 | // Compute derivatives at each point 50 | fn make_df(ys: &Vec) -> (Vec, Vec) { 51 | let df0 = (ys[1].y - ys[0].y) / (ys[1].x - ys[0].x); 52 | let mut dfs = vec![df0]; 53 | for i in 1..ys.len() - 1 { 54 | let y_m1 = ys[i - 1]; 55 | // let x0 = ys[i]; 56 | let y1 = ys[i + 1]; 57 | let dfi = (y1.y - y_m1.y) / (y1.x - y_m1.x); 58 | 59 | dfs.push(dfi); 60 | } 61 | 62 | // for the first and last points, we need to extrapolate the first derivative using the second derivative 63 | dfs[0] = dfs[1] - (ys[1].x - ys[0].x) * (dfs[2] - dfs[1]) / (ys[2].x - ys[1].x); 64 | 65 | let la = ys.len() - 1; 66 | let df_final = dfs[la - 1] 67 | - (ys[la - 1].x - ys[la].x) * (dfs[la - 2] - dfs[la - 1]) / (ys[la - 2].x - ys[la - 1].x); 68 | 69 | dfs.push(df_final); 70 | 71 | // derivatives 72 | let dfs_vec2 = dfs 73 | .iter() 74 | .map(|q| Vec2::new(1.0, *q).normalize()) 75 | .collect::>(); 76 | 77 | // normals 78 | let ns_vec2 = dfs 79 | .iter() 80 | .map(|q| Vec2::new(*q, -1.0).normalize()) 81 | .collect::>(); 82 | 83 | return (dfs_vec2, ns_vec2); 84 | } 85 | 86 | fn plot_segments( 87 | commands: &mut Commands, 88 | meshes: &mut ResMut>, 89 | segment_materials: &mut ResMut>, 90 | plot: &mut Plot, 91 | plot_handle: &Handle, 92 | ) { 93 | let data = plot.data.clone(); 94 | plot.compute_zeros(); 95 | 96 | for segment_plot in data.segment_groups.iter() { 97 | let ys = segment_plot.data.clone(); 98 | 99 | // TODO: is this still needed? 100 | // derivatives and normals 101 | let (_dfs, _ns) = make_df(&ys); 102 | 103 | let num_pts = ys.len(); 104 | 105 | let ys_world = ys.iter().map(|y| plot.to_local(*y)).collect::>(); 106 | 107 | let mut mesh0 = Vec::new(); 108 | let mut mesh_attr_uvs = Vec::new(); 109 | let mut inds = Vec::new(); 110 | let mut ends = Vec::new(); 111 | let mut mesh_attr_controls: Vec<[f32; 4]> = Vec::new(); 112 | 113 | let line_width = 5.0; 114 | for k in 0..num_pts - 1 { 115 | let y0 = ys_world[k]; 116 | let y1 = ys_world[k + 1]; 117 | 118 | let dy = (y1 - y0).normalize(); 119 | let n = Vec2::new(-dy.y, dy.x); 120 | 121 | // // short segments 122 | // let mut p0 = y0 + n * line_width; 123 | // let mut p1 = y0 - n * line_width; 124 | // let mut p2 = y1 + n * line_width; 125 | // let mut p3 = y1 - n * line_width; 126 | 127 | // if segment_plot.mech { 128 | // p0 = y0 + n * line_width - dy * line_width * 1.0; 129 | // p1 = y0 - n * line_width - dy * line_width * 1.0; 130 | // p2 = y1 + n * line_width + dy * line_width * 1.0; 131 | // p3 = y1 - n * line_width + dy * line_width * 1.0; 132 | // } 133 | 134 | // overlapping segments 135 | let p0 = y0 + n * line_width - dy * line_width * 1.0; 136 | let p1 = y0 - n * line_width - dy * line_width * 1.0; 137 | let p2 = y1 + n * line_width + dy * line_width * 1.0; 138 | let p3 = y1 - n * line_width + dy * line_width * 1.0; 139 | 140 | mesh0.push(p0); 141 | mesh0.push(p1); 142 | mesh0.push(p2); 143 | mesh0.push(p3); 144 | 145 | ends.push([y0.x, y0.y, y1.x, y1.y]); 146 | ends.push([y0.x, y0.y, y1.x, y1.y]); 147 | ends.push([y0.x, y0.y, y1.x, y1.y]); 148 | ends.push([y0.x, y0.y, y1.x, y1.y]); 149 | 150 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 151 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 152 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 153 | mesh_attr_controls.push([p0.x, p0.y, p1.x, p1.y]); 154 | 155 | mesh_attr_uvs.push([p0.x, p0.y]); 156 | mesh_attr_uvs.push([p1.x, p1.y]); 157 | mesh_attr_uvs.push([p2.x, p2.y]); 158 | mesh_attr_uvs.push([p3.x, p3.y]); 159 | 160 | let ki = k * 4; 161 | 162 | inds.push(ki as u32); 163 | inds.push((ki + 1) as u32); 164 | inds.push((ki + 2) as u32); 165 | 166 | inds.push((ki + 3) as u32); 167 | inds.push((ki + 2) as u32); 168 | inds.push((ki + 1) as u32); 169 | } 170 | 171 | let mut mesh_pos_attributes: Vec<[f32; 3]> = Vec::new(); 172 | let mut normals = Vec::new(); 173 | // TODO: z position is here 174 | for position in mesh0 { 175 | mesh_pos_attributes.push([position.x, position.y, 0.0]); 176 | normals.push([0.0, 0.0, 1.0]); 177 | } 178 | 179 | let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); 180 | 181 | mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, mesh_pos_attributes.clone()); 182 | 183 | // let mva_ends = MeshVertexAttribute::new("Ends", 1, VertexFormat::Float32x4); 184 | mesh.insert_attribute(ATTRIBUTE_ENDS, ends); 185 | 186 | mesh.set_indices(Some(Indices::U32(inds))); 187 | mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, mesh_attr_uvs); 188 | mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); 189 | 190 | // let mva_controls = MeshVertexAttribute::new("Vertext_Control", 3, VertexFormat::Float32x4); 191 | mesh.insert_attribute(ATTRIBUTE_CONTROL_POINT, mesh_attr_controls); 192 | 193 | let segment_material = SegmentUniform { 194 | mech: if segment_plot.mech { 1.0 } else { 0.0 }, 195 | segment_thickness: segment_plot.size, 196 | hole_size: 1.0, 197 | zoom: 1.0, 198 | color: col_to_vec4(segment_plot.color), 199 | inner_canvas_size_in_pixels: plot.canvas_size / (1.0 + plot.outer_border), 200 | canvas_position: plot.canvas_position, 201 | }; 202 | 203 | let segment_material_handle = segment_materials.add(segment_material); 204 | 205 | commands 206 | .spawn() 207 | // .spawn_bundle(( 208 | // SegmentMesh2d::default(), 209 | // Mesh2dHandle(meshes.add(mesh)), 210 | // GlobalTransform::default(), 211 | // Transform::from_translation(plot.canvas_position.extend(1.11)), 212 | // Visibility::default(), 213 | // ComputedVisibility::default(), 214 | // )) 215 | .insert_bundle(MaterialMesh2dBundle { 216 | mesh: Mesh2dHandle(meshes.add(mesh)), 217 | material: segment_material_handle.clone(), 218 | transform: Transform::from_translation(plot.canvas_position.extend(1.11)), 219 | ..Default::default() 220 | }) 221 | .insert(plot_handle.clone()); 222 | // .insert(); 223 | } 224 | } 225 | 226 | // /// A marker component for colored 2d meshes 227 | // #[derive(Component, Default)] 228 | // pub(crate) struct SegmentMesh2d; 229 | 230 | /// Shader uniform parameters sent to segments shader 231 | #[derive(TypeUuid, Component, Clone, AsBindGroup, ShaderType)] 232 | #[uuid = "b3124a59-8d5c-41e0-9fff-6cb0b5ab010b"] 233 | pub(crate) struct SegmentUniform { 234 | #[uniform(0)] 235 | pub color: Vec4, 236 | /// gives segments a mechanical joint look if > 0.5 237 | #[uniform(0)] 238 | pub mech: f32, 239 | #[uniform(0)] 240 | pub segment_thickness: f32, 241 | /// unused 242 | #[uniform(0)] 243 | pub hole_size: f32, 244 | #[uniform(0)] 245 | pub zoom: f32, 246 | #[uniform(0)] 247 | pub inner_canvas_size_in_pixels: Vec2, 248 | #[uniform(0)] 249 | pub canvas_position: Vec2, 250 | } 251 | 252 | // struct SegmentMesh2dPipeline { 253 | // pub view_layout: BindGroupLayout, 254 | // pub mesh_layout: BindGroupLayout, 255 | // pub custom_uniform_layout: BindGroupLayout, 256 | 257 | // // This dummy white texture is to be used in place of optional textures 258 | // #[allow(dead_code)] 259 | // pub dummy_white_gpu_image: GpuImage, 260 | // // pub shader_handle: Handle, 261 | // } 262 | 263 | // impl FromWorld for SegmentMesh2dPipeline { 264 | // fn from_world(world: &mut World) -> Self { 265 | // let mesh2d_pipeline = Mesh2dPipeline::from_world(world).clone(); 266 | 267 | // let render_device = world.get_resource::().unwrap(); 268 | 269 | // let custom_uniform_layout = 270 | // render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { 271 | // entries: &[BindGroupLayoutEntry { 272 | // binding: 0, 273 | // visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, 274 | // ty: BindingType::Buffer { 275 | // ty: BufferBindingType::Uniform, 276 | // has_dynamic_offset: true, 277 | // min_binding_size: BufferSize::new(SegmentUniform::min_size().into()), 278 | // }, 279 | // count: None, 280 | // }], 281 | // label: Some("custom_uniform_layout"), 282 | // }); 283 | 284 | // // let world = world.cell(); 285 | // // let asset_server = world.get_resource::().unwrap(); 286 | 287 | // // let shader_handle = asset_server.load("../assets/shaders/segments.wgsl"); 288 | 289 | // Self { 290 | // view_layout: mesh2d_pipeline.view_layout, 291 | // mesh_layout: mesh2d_pipeline.mesh_layout, 292 | // custom_uniform_layout, 293 | // dummy_white_gpu_image: mesh2d_pipeline.dummy_white_gpu_image, 294 | // } 295 | // } 296 | // } 297 | 298 | // #[derive(Debug, Clone, Hash, PartialEq, Eq)] 299 | // struct SegmentPipelineKey { 300 | // mesh: Mesh2dPipelineKey, 301 | // shader_handle: Handle, 302 | // } 303 | 304 | // // We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline` 305 | // impl SpecializedMeshPipeline for SegmentMesh2dPipeline { 306 | // type Key = SegmentPipelineKey; 307 | 308 | // fn specialize( 309 | // &self, 310 | // key: Self::Key, 311 | // layout: &MeshVertexBufferLayout, 312 | // ) -> Result { 313 | // // Customize how to store the meshes' vertex attributes in the vertex buffer 314 | // // Our meshes only have position and color 315 | // let vertex_attributes = vec![ 316 | // // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) 317 | // VertexAttribute { 318 | // format: VertexFormat::Float32x3, 319 | // // this offset is the size of the color attribute, which is stored first 320 | // offset: 16, 321 | // // position is available at location 0 in the shader 322 | // shader_location: 0, 323 | // }, 324 | // // Color 325 | // VertexAttribute { 326 | // format: VertexFormat::Float32x4, 327 | // offset: 0, 328 | // shader_location: 1, 329 | // }, 330 | // // uv 331 | // VertexAttribute { 332 | // format: VertexFormat::Float32x2, 333 | // offset: 28, 334 | // shader_location: 2, 335 | // }, 336 | // // Control Point 337 | // VertexAttribute { 338 | // format: VertexFormat::Float32x4, 339 | // offset: 36, 340 | // shader_location: 3, 341 | // }, 342 | // ]; 343 | // // This is the sum of the size of position, color uv attributes (12 + 16 + 8 + 8 = 44) 344 | // let vertex_array_stride = 52; 345 | 346 | // Ok(RenderPipelineDescriptor { 347 | // vertex: VertexState { 348 | // // Use our custom shader 349 | // shader: key.shader_handle.clone(), 350 | // entry_point: "vertex".into(), 351 | // shader_defs: Vec::new(), 352 | // // Use our custom vertex buffer 353 | // buffers: vec![VertexBufferLayout { 354 | // array_stride: vertex_array_stride, 355 | // step_mode: VertexStepMode::Vertex, 356 | // attributes: vertex_attributes, 357 | // }], 358 | // }, 359 | // fragment: Some(FragmentState { 360 | // // Use our custom shader 361 | // shader: key.shader_handle.clone(), 362 | // shader_defs: Vec::new(), 363 | // entry_point: "fragment".into(), 364 | // targets: vec![Some(ColorTargetState { 365 | // format: TextureFormat::bevy_default(), 366 | // blend: Some(BlendState::ALPHA_BLENDING), 367 | // write_mask: ColorWrites::ALL, 368 | // })], 369 | // }), 370 | // // Use the two standard uniforms for 2d meshes 371 | // layout: Some(vec![ 372 | // // Bind group 0 is the view uniform 373 | // self.view_layout.clone(), 374 | // // Bind group 1 is the mesh uniform 375 | // self.mesh_layout.clone(), 376 | // self.custom_uniform_layout.clone(), 377 | // // texture 378 | // // self.material_layout.clone(), 379 | // ]), 380 | // primitive: PrimitiveState { 381 | // front_face: FrontFace::Ccw, 382 | // cull_mode: Some(Face::Back), 383 | // unclipped_depth: false, 384 | // polygon_mode: PolygonMode::Fill, 385 | // conservative: false, 386 | // topology: key.mesh.primitive_topology(), 387 | // strip_index_format: None, 388 | // }, 389 | // depth_stencil: None, 390 | // multisample: MultisampleState { 391 | // count: key.mesh.msaa_samples(), 392 | // mask: !0, 393 | // alpha_to_coverage_enabled: false, 394 | // }, 395 | // label: Some("colored_mesh2d_pipeline".into()), 396 | // }) 397 | // } 398 | // } 399 | 400 | // // This specifies how to render a colored 2d mesh 401 | // type DrawSegmentMesh2d = ( 402 | // // Set the pipeline 403 | // SetItemPipeline, 404 | // // Set the view uniform as bind group 0 405 | // SetMesh2dViewBindGroup<0>, 406 | // // Set the mesh uniform as bind group 1 407 | // SetMesh2dBindGroup<1>, 408 | // SetSegmentUniformBindGroup<2>, 409 | // // Draw the mesh 410 | // DrawMesh2d, 411 | // ); 412 | 413 | /// Plugin that renders [`SegmentMesh2d`]s 414 | pub(crate) struct SegmentMesh2dPlugin; 415 | 416 | pub(crate) struct SegmentShaderHandle(pub Handle); 417 | 418 | pub const SEGMENT_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64( 419 | bevy::render::render_resource::Shader::TYPE_UUID, 420 | 5493029648115043164, 421 | ); 422 | 423 | const ATTRIBUTE_ENDS: MeshVertexAttribute = 424 | MeshVertexAttribute::new("Ends", 335119774, VertexFormat::Float32x4); 425 | 426 | const ATTRIBUTE_CONTROL_POINT: MeshVertexAttribute = 427 | MeshVertexAttribute::new("Vertext_Control", 465542875, VertexFormat::Float32x4); 428 | 429 | impl Plugin for SegmentMesh2dPlugin { 430 | fn build(&self, app: &mut App) { 431 | let mut shaders = app.world.get_resource_mut::>().unwrap(); 432 | 433 | let handle_untyped = SEGMENT_SHADER_HANDLE.clone(); 434 | 435 | shaders.set_untracked( 436 | handle_untyped.clone(), 437 | Shader::from_wgsl(include_str!("segments.wgsl")), 438 | ); 439 | 440 | // let shader_typed_handle = shaders.get_handle(handle_untyped); 441 | 442 | // app.add_plugin(UniformComponentPlugin::::default()); 443 | 444 | app.add_plugin(Material2dPlugin::::default()); 445 | 446 | // let render_app = app.get_sub_app_mut(RenderApp).unwrap(); 447 | // render_app 448 | // .add_render_command::() 449 | // .init_resource::() 450 | // .init_resource::>() 451 | // .insert_resource(SegmentShaderHandle(shader_typed_handle)) 452 | // .add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d) 453 | // .add_system_to_stage(RenderStage::Queue, queue_customuniform_bind_group) 454 | // .add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d); 455 | } 456 | } 457 | 458 | impl bevy::sprite::Material2d for SegmentUniform { 459 | fn vertex_shader() -> ShaderRef { 460 | let handle_untyped = SEGMENT_SHADER_HANDLE.clone(); 461 | let shader_handle: Handle = handle_untyped.typed::(); 462 | shader_handle.into() 463 | } 464 | fn fragment_shader() -> ShaderRef { 465 | let handle_untyped = SEGMENT_SHADER_HANDLE.clone(); 466 | let shader_handle: Handle = handle_untyped.typed::(); 467 | shader_handle.into() 468 | } 469 | 470 | fn specialize( 471 | descriptor: &mut RenderPipelineDescriptor, 472 | layout: &MeshVertexBufferLayout, 473 | _key: Material2dKey, 474 | ) -> Result<(), SpecializedMeshPipelineError> { 475 | let vertex_layout = layout.get_layout(&[ 476 | Mesh::ATTRIBUTE_POSITION.at_shader_location(0), 477 | // ATTRIBUTE_COLOR.at_shader_location(1), 478 | ATTRIBUTE_ENDS.at_shader_location(1), 479 | Mesh::ATTRIBUTE_UV_0.at_shader_location(2), 480 | ATTRIBUTE_CONTROL_POINT.at_shader_location(3), 481 | ])?; 482 | descriptor.vertex.buffers = vec![vertex_layout]; 483 | Ok(()) 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /src/segments/segments.wgsl: -------------------------------------------------------------------------------- 1 | // Import the standard 2d mesh uniforms and set their bind groups 2 | // #import bevy_sprite::mesh2d_view_bind_group 3 | // #import bevy_sprite::mesh2d_types 4 | // #import bevy_sprite::mesh2d_view_types 5 | 6 | #import bevy_sprite::mesh2d_bindings 7 | #import bevy_sprite::mesh2d_view_bindings 8 | 9 | 10 | struct SegmentUniform { 11 | color: vec4, 12 | mech: f32, 13 | segment_thickness: f32, 14 | hole_size: f32, 15 | zoom: f32, 16 | inner_canvas_size_in_pixels: vec2, 17 | canvas_position_in_pixels: vec2, 18 | }; 19 | 20 | // @group(0) @binding(0) 21 | // var view: View; 22 | 23 | @group(1) @binding(0) 24 | var uni: SegmentUniform; 25 | 26 | // @group(2) @binding(0) 27 | // var mesh: Mesh2d; 28 | 29 | 30 | 31 | // The structure of the vertex buffer is as specified in `specialize()` 32 | struct Vertex { 33 | @location(0) position: vec3, 34 | @location(1) ends: vec4, 35 | @location(2) uv: vec2, 36 | @location(3) control: vec4, 37 | }; 38 | 39 | struct VertexOutput { 40 | // The vertex shader must set the on-screen position of the vertex 41 | @builtin(position) clip_position: vec4, 42 | // We pass the vertex color to the framgent shader in location 0 43 | @location(0) ends: vec4, 44 | @location(1) uv: vec2, 45 | @location(2) control: vec4, 46 | }; 47 | 48 | @vertex 49 | fn vertex(vertex: Vertex) -> VertexOutput { 50 | 51 | var out: VertexOutput; 52 | 53 | out.clip_position = view.view_proj * mesh.model * vec4(vertex.position, 1.0); 54 | 55 | out.ends = vertex.ends; 56 | out.uv = vertex.uv; 57 | out.control = vertex.control; 58 | 59 | return out; 60 | } 61 | 62 | fn fromLinear(linearRGB: vec4) -> vec4 { 63 | let cutoff: vec4 = vec4(linearRGB < vec4(0.0031308)); 64 | let higher: vec4 = vec4(1.055) * pow(linearRGB, vec4(1.0 / 2.4)) - vec4(0.055); 65 | let lower: vec4 = linearRGB * vec4(12.92); 66 | 67 | return mix(higher, lower, cutoff); 68 | } 69 | 70 | // Converts a color from sRGB gamma to linear light gamma 71 | fn toLinear(sRGB: vec4) -> vec4 { 72 | let cutoff = vec4(sRGB < vec4(0.04045)); 73 | let higher = pow((sRGB + vec4(0.055)) / vec4(1.055), vec4(2.4)); 74 | let lower = sRGB / vec4(12.92); 75 | 76 | return mix(higher, lower, cutoff); 77 | } 78 | 79 | 80 | struct FragmentInput { 81 | @location(0) ends: vec4, 82 | @location(1) uv: vec2, 83 | @location(2) control: vec4, 84 | }; 85 | 86 | fn cla(mi: f32, ma: f32, x: f32) -> f32 { 87 | if x < mi { 88 | return mi; 89 | } 90 | if x > ma { 91 | return ma; 92 | } 93 | return x; 94 | } 95 | 96 | fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { 97 | let pa = p - a; 98 | let ba = b - a; 99 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.); 100 | return length(pa - ba * h); 101 | } 102 | 103 | 104 | fn sdLine(uv: vec2, p0: vec2, p1: vec2) -> f32 { 105 | let m = (p1.y - p0.y) / (p1.x - p0.x); 106 | let b = p0.y - m * p0.x; 107 | let biga = m; 108 | let bigc = b; 109 | 110 | let d = abs(-m * uv.x + uv.y - b) / sqrt(m * m + 1.0); 111 | return d; 112 | } 113 | 114 | fn sdCircle(p: vec2, c: vec2, r: f32) -> f32 { 115 | let d = length(p - c); 116 | return d - r; 117 | } 118 | 119 | 120 | fn sdRoundedBox(p: vec2, b: vec2, r: vec4) -> f32 { 121 | var x = r.x; 122 | var y = r.y; 123 | x = select(r.z, r.x, p.x > 0.); 124 | y = select(r.w, r.y, p.x > 0.); 125 | x = select(y, x, p.y > 0.); 126 | let q = abs(p) - b + x; 127 | return min(max(q.x, q.y), 0.) + length(max(q, vec2(0.))) - x; 128 | } 129 | 130 | // fn sdBox(p: vec2, b: vec2) -> f32 { 131 | // let d = (abs(p) - b) ; 132 | // return length(max(d, vec2(0.))) + min(max(d.x, d.y), 0.); 133 | // } 134 | 135 | 136 | 137 | 138 | @fragment 139 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 140 | let width = 1.0 ; 141 | 142 | let zoom = uni.zoom; 143 | var w = width * zoom * sqrt(uni.segment_thickness * 4.5) ; 144 | 145 | var solid = width * zoom * uni.segment_thickness ; 146 | 147 | var out_col = uni.color; 148 | 149 | let y0 = in.ends.xy; 150 | let y1 = in.ends.zw; 151 | 152 | let dy = normalize(y1 - y0); 153 | let q0 = y0 - dy * 10.0; 154 | let q1 = y1 + dy * 10.0; 155 | 156 | 157 | // change the aliasing as a function of the zoom 158 | var circ_zoom = zoom; 159 | 160 | if zoom > .0 { 161 | circ_zoom = pow(zoom, 0.05); 162 | } 163 | 164 | if zoom < 1.0 { 165 | circ_zoom = sqrt(sqrt(zoom)); 166 | } 167 | 168 | 169 | let d = sdSegment(in.uv, y0, y1) ; 170 | let s = smoothstep(solid, solid + w, d); 171 | out_col = out_col * (1.0 - s); 172 | 173 | 174 | // mechanical look 175 | if uni.mech > 0.5 { 176 | let c0 = sdCircle(in.uv, y0, w); 177 | let sc0 = smoothstep(0.0 + solid, w + solid, c0); 178 | 179 | let solid_c = solid / 3.0; 180 | let w_c = w / 2.0; 181 | 182 | let c1 = sdCircle(in.uv, y1, 0.2); 183 | let sc1 = smoothstep(0.0 + solid_c, (w_c + solid_c), abs(c1)); 184 | 185 | out_col.a = out_col.a * (1.0 - s) * (sc1) * sc0; 186 | } 187 | 188 | 189 | // mask with the canvas 190 | let r = 0.02 * uni.inner_canvas_size_in_pixels.x; 191 | let d = sdRoundedBox( 192 | // in.uv - bez_uni.canvas_position_in_pixels , 193 | in.uv + uni.canvas_position_in_pixels * 0.0, 194 | uni.inner_canvas_size_in_pixels / 2.0 - 1.0, 195 | vec4(r, r, r, r) 196 | ); 197 | 198 | let s = smoothstep(-2.0, 0.0, d); 199 | out_col = mix(out_col, vec4(out_col.x, out_col.y, out_col.z, 0.0), s) ; 200 | 201 | 202 | return out_col; 203 | 204 | // return vec4(1.0,0.0,0.0,1.0); 205 | } -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | // use std::collections::HashMap; 4 | 5 | #[derive(Event)] 6 | pub(crate) struct ReleaseAllEvent; 7 | 8 | #[derive(Component)] 9 | pub(crate) struct Locked; 10 | 11 | pub(crate) fn col_to_vec4(col: Color) -> Vec4 { 12 | let linear = col.to_linear(); 13 | Vec4::new(linear.red, linear.green, linear.blue, linear.alpha) 14 | } 15 | -------------------------------------------------------------------------------- /stiched.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliotbo/bevy_plot/e84802f50d4b5cda0d0fcf780a2b91f3cf144f05/stiched.gif --------------------------------------------------------------------------------