rgeometry_wasm/
lib.rs

1use std::sync::atomic::AtomicI32;
2use std::sync::atomic::Ordering::*;
3
4// mod timer {
5//   pub struct Timer {
6//     started: Option<f64>,
7//     paused: Option<f64>,
8//   }
9//   // pause(f64)
10//   // resume(f64)
11//   // reset(f64)
12//   // read() -> f64
13//   // update(f64)
14// }
15
16static MOUSE_X: AtomicI32 = AtomicI32::new(0);
17static MOUSE_Y: AtomicI32 = AtomicI32::new(0);
18
19fn get_mouse() -> (i32, i32) {
20  (MOUSE_X.load(Relaxed), MOUSE_Y.load(Relaxed))
21}
22
23fn set_mouse(x: i32, y: i32) {
24  MOUSE_X.store(x, Relaxed);
25  MOUSE_Y.store(y, Relaxed);
26}
27
28pub mod playground {
29  use rgeometry::algorithms::polygonization::{resolve_self_intersections, two_opt_moves};
30  use rgeometry::data::*;
31
32  use gloo_events::{EventListener, EventListenerOptions};
33  use ordered_float::OrderedFloat;
34  use rand::distributions::Standard;
35  // use rand::distributions::Uniform;
36  use rand::Rng;
37  use std::ops::Deref;
38  // use std::ops::DerefMut;
39  use std::ops::Index;
40  use std::sync::Once;
41  use wasm_bindgen::{JsCast, UnwrapThrowExt};
42  use web_sys::Path2d;
43
44  use once_cell::sync::Lazy;
45  use once_cell::sync::OnceCell;
46  use std::sync::Mutex;
47
48  pub type Num = OrderedFloat<f64>;
49
50  pub fn upd_mouse(event: &web_sys::MouseEvent) {
51    super::set_mouse(event.offset_x(), event.offset_y())
52  }
53
54  pub fn upd_touch(event: &web_sys::TouchEvent) {
55    let x = event.touches().get(0).unwrap().client_x();
56    let y = event.touches().get(0).unwrap().client_y();
57    super::set_mouse(x, y)
58  }
59
60  pub fn get_device_pixel_ratio() -> f64 {
61    web_sys::window().unwrap().device_pixel_ratio()
62  }
63  pub fn document() -> web_sys::Document {
64    web_sys::window().unwrap().document().unwrap()
65  }
66  pub fn canvas() -> web_sys::HtmlCanvasElement {
67    let canvas = document().get_element_by_id("canvas").unwrap();
68    let canvas: web_sys::HtmlCanvasElement = canvas
69      .dyn_into::<web_sys::HtmlCanvasElement>()
70      .map_err(|_| ())
71      .unwrap();
72    canvas
73  }
74
75  pub fn context() -> web_sys::CanvasRenderingContext2d {
76    canvas()
77      .get_context("2d")
78      .unwrap()
79      .unwrap()
80      .dyn_into::<web_sys::CanvasRenderingContext2d>()
81      .unwrap()
82  }
83
84  pub fn clear_screen() {
85    let canvas = canvas();
86    let context = context();
87    context.save();
88    context.reset_transform().unwrap();
89    context.clear_rect(0., 0., canvas.width() as f64, canvas.height() as f64);
90    context.restore();
91  }
92
93  pub fn absolute_mouse_position() -> (i32, i32) {
94    super::get_mouse()
95  }
96
97  pub fn mouse_position() -> (f64, f64) {
98    let (x, y) = absolute_mouse_position();
99    inv_canvas_position(x, y)
100  }
101
102  pub fn inv_canvas_position(x: i32, y: i32) -> (f64, f64) {
103    let ratio = get_device_pixel_ratio();
104    let context = context();
105    let transform = &context.get_transform().unwrap();
106    let inv = transform.inverse();
107    let mut pt = web_sys::DomPointInit::new();
108    pt.x(x as f64 * ratio);
109    pt.y(y as f64 * ratio);
110    let out = inv.transform_point_with_point(&pt);
111    (out.x(), out.y())
112  }
113
114  pub fn from_pixels(pixels: u32) -> f64 {
115    let (vw, vh) = get_viewport();
116    let canvas = canvas();
117    let ratio = get_device_pixel_ratio();
118    if vw < vh {
119      (vw / canvas.width() as f64) * pixels as f64 * ratio
120    } else {
121      (vh / canvas.height() as f64) * pixels as f64 * ratio
122    }
123  }
124  pub fn get_viewport() -> (f64, f64) {
125    let canvas = canvas();
126    let context = context();
127    let transform = context.get_transform().unwrap();
128    let scale = transform.a();
129    // let ratio = get_device_pixel_ratio();
130    (
131      canvas.width() as f64 / scale,
132      canvas.height() as f64 / scale,
133    )
134  }
135  pub fn set_viewport(width: f64, height: f64) {
136    let pixel_ratio = get_device_pixel_ratio();
137    let canvas = canvas();
138    let context = context();
139
140    context.reset_transform().unwrap();
141
142    let ratio_width = canvas.width() as f64 / width;
143    let ratio_height = canvas.height() as f64 / height;
144    let ratio = if ratio_width < ratio_height {
145      ratio_width
146    } else {
147      ratio_height
148    };
149    context.scale(ratio, -ratio).unwrap();
150    context
151      .translate(
152        canvas.width() as f64 / ratio / 2.,
153        -(canvas.height() as f64 / ratio / 2.),
154      )
155      .unwrap();
156    context.set_line_width(2. / ratio * pixel_ratio);
157  }
158
159  pub fn render_polygon(poly: &Polygon<Num>) {
160    let context = context();
161
162    context.begin_path();
163    context.set_line_join("round");
164    let mut iter = poly.iter_boundary().map(|pt| pt.point());
165    if let Some(origin) = iter.next() {
166      let [x, y] = origin.array;
167      context.move_to(*x, *y);
168      for pt in iter {
169        let [x2, y2] = pt.array;
170        context.line_to(*x2, *y2);
171      }
172    }
173    context.close_path();
174    context.fill();
175    context.stroke();
176  }
177
178  pub fn render_line(pts: &[Point<Num, 2>]) {
179    let context = context();
180
181    context.begin_path();
182    context.set_line_join("round");
183    let mut iter = pts.iter();
184    if let Some(origin) = iter.next() {
185      let [x, y] = origin.array;
186      context.move_to(*x, *y);
187      for pt in iter {
188        let [x2, y2] = pt.array;
189        context.line_to(*x2, *y2);
190      }
191    }
192    context.stroke();
193  }
194
195  pub fn point_path_2d(pt: &Point<Num, 2>, scale: f64) -> Path2d {
196    let path = Path2d::new().unwrap();
197    path
198      .arc(
199        **pt.x_coord(),
200        **pt.y_coord(),
201        scale * from_pixels(15), // radius
202        0.0,
203        std::f64::consts::PI * 2.,
204      )
205      .unwrap();
206    path
207  }
208
209  pub fn at_point<F: FnOnce()>(pt: &Point<Num, 2>, cb: F) {
210    let context = context();
211    context.save();
212    context.translate(**pt.x_coord(), **pt.y_coord()).unwrap();
213    cb();
214    context.restore();
215  }
216
217  pub fn circle(radius: u32) -> Path2d {
218    let path = Path2d::new().unwrap();
219    path
220      .arc(
221        0.0,
222        0.0,
223        from_pixels(radius), // radius
224        0.0,
225        std::f64::consts::PI * 2.,
226      )
227      .unwrap();
228    path
229  }
230
231  pub fn render_point(pt: &Point<Num, 2>) {
232    let path = point_path_2d(pt, 1.0);
233
234    set_fill_style("green");
235    fill_with_path_2d(&path);
236    stroke_with_path(&path);
237  }
238
239  pub fn render_fixed_point(pt: &Point<Num, 2>) {
240    let path = point_path_2d(pt, 0.5);
241
242    set_fill_style("grey");
243    stroke_with_path(&path);
244    fill_with_path_2d(&path);
245  }
246
247  // #[deprecated(since = "0.1.0", note = "Please use the get_points function instead")]
248  pub fn with_points(n: usize) -> Vec<Point<Num, 2>> {
249    get_points(n)
250  }
251
252  pub fn get_points(n: usize) -> Vec<Point<Num, 2>> {
253    static SELECTED: Lazy<Mutex<Option<(usize, i32, i32)>>> = Lazy::new(|| Mutex::new(None));
254    static POINTS: Lazy<Mutex<Vec<Point<Num, 2>>>> = Lazy::new(|| Mutex::new(vec![]));
255
256    static START: Once = Once::new();
257
258    START.call_once(|| {
259      {
260        let mut pts = POINTS.lock().unwrap();
261        let mut rng = rand::thread_rng();
262        let (width, height) = get_viewport();
263        let t = Transform::scale(Vector([
264          OrderedFloat(width * 0.8),
265          OrderedFloat(height * 0.8),
266        ]))
267          * Transform::translate(Vector([OrderedFloat(-0.5), OrderedFloat(-0.5)]));
268        while pts.len() < n {
269          let pt: Point<Num, 2> = rng.sample(Standard);
270          let pt = &t * pt;
271          pts.push(pt)
272        }
273      }
274
275      let handle_select = || {
276        let (x, y) = absolute_mouse_position();
277        let ratio = get_device_pixel_ratio();
278        let context = context();
279        let pts = POINTS.lock().unwrap();
280
281        for (i, pt) in pts.deref().iter().enumerate() {
282          let path = point_path_2d(pt, 1.0);
283          let in_path = context.is_point_in_path_with_path_2d_and_f64(
284            &path,
285            x as f64 * ratio,
286            y as f64 * ratio,
287          );
288          let in_stroke = context.is_point_in_stroke_with_path_and_x_and_y(
289            &path,
290            x as f64 * ratio,
291            y as f64 * ratio,
292          );
293          if in_path || in_stroke {
294            let mut selected = SELECTED.lock().unwrap();
295            *selected = Some((i, x, y));
296            break;
297          }
298        }
299      };
300      on_mousedown(move |event| {
301        upd_mouse(event);
302        handle_select();
303      });
304      on_touchstart(move |event| {
305        upd_touch(event);
306        handle_select();
307      });
308      on_mouseup(|_event| *SELECTED.lock().unwrap() = None);
309      on_touchend(|_event| *SELECTED.lock().unwrap() = None);
310      on_touchmove(move |event| {
311        if SELECTED.lock().unwrap().is_some() {
312          event.prevent_default();
313        }
314      });
315    });
316
317    // Update points if mouse moved.
318    {
319      let mut selected = SELECTED.lock().unwrap();
320
321      let (mouse_x, mouse_y) = absolute_mouse_position();
322      if let Some((i, x, y)) = *selected {
323        let (x, y) = inv_canvas_position(x, y);
324        let (ox, oy) = inv_canvas_position(mouse_x, mouse_y);
325        let dx = (ox - x) as f64;
326        let dy = (oy - y) as f64;
327        *selected = Some((i, mouse_x, mouse_y));
328
329        let mut pts = POINTS.lock().unwrap();
330        let pt = pts.index(i);
331        let vector: Vector<Num, 2> = Vector([OrderedFloat(dx), OrderedFloat(dy)]);
332        pts[i] = pt + &vector;
333      }
334    }
335
336    POINTS.lock().unwrap().clone()
337  }
338
339  pub fn with_polygon(n: usize) -> Polygon<Num> {
340    get_polygon(n)
341  }
342
343  pub fn get_polygon(n: usize) -> Polygon<Num> {
344    static POLYGON: OnceCell<Mutex<Polygon<Num>>> = OnceCell::new();
345    let mut p = POLYGON
346      .get_or_init(|| {
347        let pts = with_points(n);
348        let p = two_opt_moves(pts, &mut rand::thread_rng()).unwrap();
349        Mutex::new(p)
350      })
351      .lock()
352      .unwrap();
353
354    let pts = with_points(n);
355
356    for (idx, pt) in p.iter_mut().enumerate() {
357      *pt = pts[idx].clone();
358    }
359    resolve_self_intersections(&mut p, &mut rand::thread_rng()).unwrap();
360    p.clone()
361  }
362
363  pub fn on_canvas_click<F>(callback: F)
364  where
365    F: Fn() + 'static,
366  {
367    let canvas = super::playground::canvas();
368    let listener = EventListener::new(&canvas, "click", move |_event| callback());
369    listener.forget();
370  }
371
372  pub fn on_mousemove<F>(callback: F)
373  where
374    F: Fn(&web_sys::MouseEvent) + 'static,
375  {
376    let canvas = super::playground::canvas();
377    let listener = EventListener::new(&canvas, "mousemove", move |event| {
378      let event = event.dyn_ref::<web_sys::MouseEvent>().unwrap_throw();
379      callback(event)
380    });
381    listener.forget();
382  }
383  pub fn on_mousedown<F>(callback: F)
384  where
385    F: Fn(&web_sys::MouseEvent) + 'static,
386  {
387    let canvas = super::playground::canvas();
388    let listener = EventListener::new(&canvas, "mousedown", move |event| {
389      let event = event.dyn_ref::<web_sys::MouseEvent>().unwrap_throw();
390      callback(event)
391    });
392    listener.forget();
393  }
394  pub fn on_mouseup<F>(callback: F)
395  where
396    F: Fn(&web_sys::MouseEvent) + 'static,
397  {
398    let canvas = super::playground::canvas();
399    let listener = EventListener::new(&canvas, "mouseup", move |event| {
400      let event = event.dyn_ref::<web_sys::MouseEvent>().unwrap_throw();
401      callback(event)
402    });
403    listener.forget();
404  }
405
406  pub fn on_touchstart<F>(callback: F)
407  where
408    F: Fn(&web_sys::TouchEvent) + 'static,
409  {
410    let options = EventListenerOptions::enable_prevent_default();
411    let canvas = super::playground::canvas();
412    let listener = EventListener::new_with_options(&canvas, "touchstart", options, move |event| {
413      let event = event.dyn_ref::<web_sys::TouchEvent>().unwrap_throw();
414      callback(event)
415    });
416    listener.forget();
417  }
418
419  pub fn on_touchend<F>(callback: F)
420  where
421    F: Fn(&web_sys::TouchEvent) + 'static,
422  {
423    let options = EventListenerOptions::enable_prevent_default();
424    let canvas = super::playground::canvas();
425    let listener = EventListener::new_with_options(&canvas, "touchend", options, move |event| {
426      let event = event.dyn_ref::<web_sys::TouchEvent>().unwrap_throw();
427      callback(event)
428    });
429    listener.forget();
430  }
431
432  pub fn on_touchmove<F>(callback: F)
433  where
434    F: Fn(&web_sys::TouchEvent) + 'static,
435  {
436    let options = EventListenerOptions::enable_prevent_default();
437    let canvas = super::playground::canvas();
438    let listener = EventListener::new_with_options(&canvas, "touchmove", options, move |event| {
439      let event = event.dyn_ref::<web_sys::TouchEvent>().unwrap_throw();
440      callback(event)
441    });
442    listener.forget();
443  }
444
445  mod context {
446    use super::{context, from_pixels, Num};
447    use js_sys::Array;
448    use rgeometry::data::*;
449    use web_sys::Path2d;
450
451    pub fn set_font(font: &str) {
452      context().set_font(font)
453    }
454
455    pub fn set_text_align(align: &str) {
456      context().set_text_align(align)
457    }
458
459    pub fn set_text_baseline(baseline: &str) {
460      context().set_text_baseline(baseline)
461    }
462
463    pub fn set_fill_style(style: &str) {
464      context().set_fill_style(&style.into())
465    }
466
467    pub fn set_stroke_style(style: &str) {
468      context().set_stroke_style(&style.into())
469    }
470
471    pub fn fill() {
472      context().fill()
473    }
474
475    pub fn stroke() {
476      context().stroke()
477    }
478
479    pub fn fill_text(text: &str) {
480      context().save();
481      let factor = from_pixels(1);
482      context().scale(factor, -factor).unwrap();
483      context().fill_text(text, 0.0, 0.0).unwrap();
484      context().restore();
485    }
486
487    pub fn stroke_text(text: &str) {
488      context().save();
489      let factor = from_pixels(1);
490      context().scale(factor, -factor).unwrap();
491      context().stroke_text(text, 0.0, 0.0).unwrap();
492      context().restore();
493    }
494
495    pub fn fill_with_path_2d(path: &Path2d) {
496      context().fill_with_path_2d(path)
497    }
498
499    pub fn stroke_with_path(path: &Path2d) {
500      context().stroke_with_path(path)
501    }
502
503    pub fn begin_path() {
504      context().begin_path();
505    }
506
507    pub fn close_path() {
508      context().close_path();
509    }
510
511    pub fn set_line_join(join: &str) {
512      context().set_line_join(join)
513    }
514
515    pub fn move_to(x: f64, y: f64) {
516      context().move_to(x, y)
517    }
518
519    pub fn move_to_point(pt: &Point<Num, 2>) {
520      move_to(**pt.x_coord(), **pt.y_coord())
521    }
522
523    pub fn line_to(x: f64, y: f64) {
524      context().line_to(x, y)
525    }
526
527    pub fn line_to_point(pt: &Point<Num, 2>) {
528      line_to(**pt.x_coord(), **pt.y_coord())
529    }
530
531    pub fn set_line_dash(dash: &[f64]) {
532      let arr = Array::new();
533      for (nth, &dash_len) in dash.iter().enumerate() {
534        arr.set(nth as u32, dash_len.into());
535      }
536      context().set_line_dash(arr.as_ref()).unwrap()
537    }
538  }
539  pub use context::*;
540}