1use std::sync::atomic::AtomicI32;
2use std::sync::atomic::Ordering::*;
3
4static 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::Rng;
37 use std::ops::Deref;
38 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 (
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), 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), 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 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 {
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}