1use super::{get_filler, Op, OpSet, OpSetType, Random, RoughOptions};
2use crate::{OpType, Point};
3use std::f64::consts::PI;
4pub struct EllipseResult {
8 pub opset: OpSet,
9 pub estimated_points: Vec<Point<f64>>,
10}
11
12pub struct EllipseParams {
13 pub rx: f64,
14 pub ry: f64,
15 pub increment: f64,
16}
17
18pub struct Renderer;
19impl Renderer {
27 pub fn line(x1: f64, y1: f64, x2: f64, y2: f64, options: &RoughOptions) -> OpSet {
28 OpSet {
29 kind: OpSetType::Path,
30 ops: Renderer::_double_line(x1, y1, x2, y2, options, false),
31 size: None,
32 path: None,
33 }
34 }
35
36 pub fn linear_path(points: &Vec<Point<f64>>, close: bool, options: &RoughOptions) -> OpSet {
37 let len = points.len();
38 if len > 2 {
39 let mut ops: Vec<Op> = Vec::new();
40 for idx in 0..len - 1 {
41 let first = points.get(idx).unwrap();
42 let next = points.get(idx + 1).unwrap();
43 let mut op2 =
44 Renderer::_double_line(first.x, first.y, next.x, next.y, options, false);
45 ops.append(op2.as_mut());
46 }
47
48 if close {
49 let first = points.first().unwrap();
50 let last = points.last().unwrap();
51 let mut op2 =
52 Renderer::_double_line(last.x, last.y, first.x, first.y, options, false);
53 ops.append(op2.as_mut());
54 }
55
56 return OpSet {
57 kind: OpSetType::Path,
58 ops,
59 size: None,
60 path: None,
61 };
62 } else if len == 2 {
63 let first = points.first().unwrap();
64 let last = points.last().unwrap();
65 return Renderer::line(first.x, first.y, last.x, last.y, options);
66 }
67
68 OpSet {
69 kind: OpSetType::Path,
70 ops: Vec::new(),
71 size: None,
72 path: None,
73 }
74 }
75
76 pub fn polygon(points: &Vec<Point<f64>>, options: &RoughOptions) -> OpSet {
77 Renderer::linear_path(points, true, options)
78 }
79
80 pub fn rectangle(x: f64, y: f64, width: f64, height: f64, options: &RoughOptions) -> OpSet {
81 let points: Vec<Point<f64>> = vec![
82 Point::new(x, y),
83 Point::new(x + width, y),
84 Point::new(x + width, y + height),
85 Point::new(x, y + height),
86 ];
87
88 Renderer::polygon(&points, options)
89 }
90
91 pub fn curve(points: &Vec<Point<f64>>, options: &RoughOptions) -> OpSet {
92 let mut o1 =
93 Renderer::_curve_with_offset(points, 1.0 * (1.0 + options.roughness * 0.2), options);
94 if !options.disable_multi_stroke {
95 let mut o2 = Renderer::_curve_with_offset(
96 points,
97 1.5 * (1.0 + options.roughness * 0.22),
98 options, );
100 o1.append(o2.as_mut());
101 }
102
103 OpSet {
104 kind: OpSetType::Path,
105 ops: o1,
106 size: None,
107 path: None,
108 }
109 }
110
111 pub fn ellipse(x: f64, y: f64, width: f64, height: f64, options: &RoughOptions) -> OpSet {
112 let params = Renderer::generate_ellipse_params(width, height, options);
113 Renderer::ellipse_with_params(x, y, options, ¶ms).opset
114 }
115
116 pub fn generate_ellipse_params(width: f64, height: f64, options: &RoughOptions) -> EllipseParams {
117 let psq =
118 (PI * 2.0 * (((width / 2.0).powi(2) + (height / 2.0).powi(2)) / 2.0).sqrt()).sqrt();
119 let step_count = options.curve_step_count.max(options.curve_step_count / 200_f64.sqrt()) * psq;
120 let increment = (PI * 2.0) / step_count;
121 let mut rx = (width / 2.0).abs();
122 let mut ry = (height / 2.0).abs();
123 let curve_fit_randomness = 1.0 - options.curve_fitting;
124 rx += Renderer::_offset_opt(rx * curve_fit_randomness, options, 1.0);
125 ry += Renderer::_offset_opt(ry * curve_fit_randomness, options, 1.0);
126 EllipseParams { increment, rx, ry }
127 }
128
129 pub fn ellipse_with_params(
130 x: f64,
131 y: f64,
132 o: &RoughOptions,
133 ellipse_params: &EllipseParams,
134 ) -> EllipseResult {
135 let (ap1, cp1) = Renderer::_compute_ellipse_points(
136 ellipse_params.increment,
137 x,
138 y,
139 ellipse_params.rx,
140 ellipse_params.ry,
141 1.0,
142 ellipse_params.increment
143 * Renderer::_offset(0.1, Renderer::_offset(0.4, 1.0, o, 1.0), o, 1.0),
144 o,
145 );
146
147 let mut o1 = Renderer::_curve(ap1, None, o);
148 if !o.disable_multi_stroke {
149 let (ap2, _) = Renderer::_compute_ellipse_points(
150 ellipse_params.increment,
151 x,
152 y,
153 ellipse_params.rx,
154 ellipse_params.ry,
155 1.5,
156 0.0,
157 o,
158 );
159 let mut o2 = Renderer::_curve(ap2, None, o);
160 o1.append(o2.as_mut());
161 }
162
163 EllipseResult {
164 estimated_points: cp1,
165 opset: OpSet {
166 kind: OpSetType::Path,
167 ops: o1,
168 size: None,
169 path: None,
170 },
171 }
172 }
173
174 pub fn arc(
175 x: f64,
176 y: f64,
177 width: f64,
178 height: f64,
179 start: f64,
180 stop: f64,
181 closed: bool,
182 rough_closure: bool,
183 options: &RoughOptions,
184 ) -> OpSet {
185 let cx = x;
186 let cy = y;
187 let rx =
188 (width / 2.0).abs() + Renderer::_offset_opt((width / 2.0).abs() * 0.01, options, 1.0);
189 let ry =
190 (height / 2.0).abs() + Renderer::_offset_opt((height / 2.0).abs() * 0.01, options, 1.0);
191 let mut strt = start;
192 let mut stp = stop;
193 while strt < 0.0 {
194 strt += PI * 2.0;
195 stp += PI * 2.0;
196 }
197
198 if (stp - strt) > PI * 2.0 {
199 strt = 0.0;
200 stp = PI * 2.0;
201 }
202
203 let ellipse_inc = PI * 2.0 / options.curve_step_count;
204 let arc_inc = (ellipse_inc / 2.0).min((stp - strt) / 2.0);
205 let mut ops = Renderer::_arc(arc_inc, cx, cy, rx, ry, strt, stp, 1.0, options);
206
207 if !options.disable_multi_stroke {
208 let mut o2 = Renderer::_arc(arc_inc, cx, cy, rx, ry, strt, stp, 1.5, options);
210 ops.append(o2.as_mut());
211 }
212
213 if closed {
214 if rough_closure {
216 let mut op2 = Renderer::_double_line(
217 cx,
218 cy,
219 cx + rx * strt.cos(),
220 cy + ry * strt.sin(),
221 options,
222 false,
223 );
224 ops.append(op2.as_mut());
225
226 let mut op2 = Renderer::_double_line(
227 cx,
228 cy,
229 cx + rx * stp.cos(),
230 cy + ry * stp.sin(),
231 options,
232 false,
233 );
234 ops.append(op2.as_mut());
235 } else {
236 ops.push(Op {
237 op: OpType::LineTo,
238 data: vec![cx, cy],
239 });
240 ops.push(Op {
241 op: OpType::LineTo,
242 data: vec![cx + rx * strt.cos(), cy + ry * strt.sin()],
243 });
244 }
245 }
246
247 OpSet {
248 kind: OpSetType::Path,
249 ops,
250 size: None,
251 path: None,
252 }
253 }
254
255 pub fn solid_fill_polygon(points: &Vec<Point<f64>>, options: &RoughOptions) -> OpSet {
295 let mut ops: Vec<Op> = Vec::new();
296 let len = points.len();
297 if len > 0 {
298 let offset = options.max_randomness_offset;
299 if len > 2 {
300 let first = points.first().unwrap();
301 ops.push(Op {
302 op: OpType::Move,
303 data: vec![
304 first.x + Renderer::_offset_opt(offset, options, 1.0),
305 first.y + Renderer::_offset_opt(offset, options, 1.0),
306 ],
307 });
308
309 for i in 1..len {
310 let pt = points.get(i).unwrap();
311 ops.push(Op {
312 op: OpType::LineTo,
313 data: vec![
314 pt.x + Renderer::_offset_opt(offset, options, 1.0),
315 pt.y + Renderer::_offset_opt(offset, options, 1.0),
316 ],
317 });
318 }
319 }
320 }
321 OpSet {
322 kind: OpSetType::FillPath,
323 ops,
324 size: None,
325 path: None,
326 }
327 }
328
329 pub fn pattern_fill_polygon(points: &Vec<Point<f64>>, options: &RoughOptions) -> OpSet {
330 unimplemented!()
332 }
333
334 pub fn pattern_fill_arc(
335 x: f64,
336 y: f64,
337 width: f64,
338 height: f64,
339 start: f64,
340 stop: f64,
341 o: &RoughOptions,
342 ) -> OpSet {
343 let cx = x;
344 let cy = y;
345 let mut rx = (width / 2.0).abs();
346 let mut ry = (height / 2.0).abs();
347 rx += Renderer::_offset_opt(rx * 0.01, o, 1.0);
348 ry += Renderer::_offset_opt(ry * 0.01, o, 1.0);
349 let mut strt = start;
350 let mut stp = stop;
351 while strt < 0.0 {
352 strt += PI * 2.0;
353 stp += PI * 2.0;
354 }
355
356 if (stp - strt) > PI * 2.0 {
357 strt = 0.0;
358 stp = PI * 2.0;
359 }
360
361 let increment = (stp - strt) / o.curve_step_count;
362 let mut points: Vec<Point<f64>> = Vec::new();
363
364 let mut angle = strt;
365 while angle <= stp {
366 points.push(Point::new(cx + rx * angle.cos(), cy + ry * angle.sin()));
367 angle = angle + increment
368 }
369
370 points.push(Point::new(cx + rx * stp.cos(), cy + ry * stp.sin()));
371 points.push(Point::new(cx, cy));
372 Renderer::pattern_fill_polygon(&points, o)
373 }
374
375 pub fn rand_offset(x: f64, o: &RoughOptions) -> f64 {
376 Renderer::_offset_opt(x, o, 1.0)
377 }
378
379 pub fn rand_offset_with_range(min: f64, max: f64, o: &RoughOptions) -> f64 {
380 Renderer::_offset(min, max, o, 1.0)
381 }
382
383 pub fn double_line_fill_ops(x1: f64, y1: f64, x2: f64, y2: f64, o: &RoughOptions) -> Vec<Op> {
384 Renderer::_double_line(x1, y1, x2, y2, o, true)
385 }
386
387 fn randomize(ops: &RoughOptions) -> f64 {
388 rand::random()
393 }
394
395 fn _offset(min: f64, max: f64, ops: &RoughOptions, roughness_gain: f64) -> f64 {
396 ops.roughness * roughness_gain * ((Renderer::randomize(ops) * (max - min)) + min)
398 }
399
400 fn _offset_opt(x: f64, ops: &RoughOptions, roughness_gain: f64) -> f64 {
401 Renderer::_offset(-x, x, ops, roughness_gain)
403 }
404
405 fn _double_line(
406 x1: f64,
407 y1: f64,
408 x2: f64,
409 y2: f64,
410 o: &RoughOptions,
411 filling: bool,
412 ) -> Vec<Op> {
413 let single_stroke = if filling {
415 o.disable_multi_stroke_fill
416 } else {
417 o.disable_multi_stroke
418 };
419
420 let mut result = Renderer::_line(x1, y1, x2, y2, o, true, false);
421 if !single_stroke {
422 let mut second = Renderer::_line(x1, y1, x2, y2, o, true, true);
423 result.append(second.as_mut());
424 }
425
426 result
427 }
428
429 fn _line(
430 x1: f64,
431 y1: f64,
432 x2: f64,
433 y2: f64,
434 o: &RoughOptions,
435 ismove: bool,
436 overlay: bool,
437 ) -> Vec<Op> {
438 let length_sq = (x1 - x2).powi(2) + (y1 - y2).powi(2);
439 let length = length_sq.sqrt();
440
441 let roughness_gain = if length < 200.0 {
443 1.0
444 } else if length > 500.0 {
445 0.4
446 } else {
447 -0.0016668 * length + 1.233334
448 };
449
450 let mut offset = o.max_randomness_offset;
451 if (offset * offset * 100.0) > length_sq {
452 offset = length / 10.0;
453 }
454 let half_offset = offset / 2.0;
455 let diverge_point = 0.2 + Renderer::randomize(o) as f64 * 0.2;
456
457 let mut mid_disp_x = o.bowing * o.max_randomness_offset * (y2 - y1) / 200.0;
458 let mut mid_disp_y = o.bowing * o.max_randomness_offset * (x1 - x2) / 200.0;
459 mid_disp_x = Renderer::_offset_opt(mid_disp_x, o, roughness_gain);
460 mid_disp_y = Renderer::_offset_opt(mid_disp_y, o, roughness_gain);
461 let mut ops: Vec<Op> = Vec::new();
462
463 if ismove {
464 if overlay {
465 ops.push(Op {
466 op: OpType::Move,
467 data: vec![
468 x1 + Renderer::_offset_opt(half_offset, o, roughness_gain),
469 y1 + Renderer::_offset_opt(half_offset, o, roughness_gain),
470 ],
471 });
472 } else {
473 ops.push(Op {
474 op: OpType::Move,
475 data: vec![
476 x1 + Renderer::_offset_opt(offset, o, roughness_gain),
477 y1 + Renderer::_offset_opt(offset, o, roughness_gain),
478 ],
479 });
480 }
481 }
482
483 if overlay {
484 ops.push(Op {
485 op: OpType::BCurveTo,
486 data: vec![
487 mid_disp_x
488 + x1
489 + (x2 - x1) * diverge_point
490 + Renderer::_offset_opt(half_offset, o, roughness_gain),
491 mid_disp_y
492 + y1
493 + (y2 - y1) * diverge_point
494 + Renderer::_offset_opt(half_offset, o, roughness_gain),
495 mid_disp_x
496 + x1
497 + 2.0 * (x2 - x1) * diverge_point
498 + Renderer::_offset_opt(half_offset, o, roughness_gain),
499 mid_disp_y
500 + y1
501 + 2.0 * (y2 - y1) * diverge_point
502 + Renderer::_offset_opt(half_offset, o, roughness_gain),
503 x2 + Renderer::_offset_opt(half_offset, o, roughness_gain),
504 y2 + Renderer::_offset_opt(half_offset, o, roughness_gain),
505 ],
506 });
507 } else {
508 ops.push(Op {
509 op: OpType::BCurveTo,
510 data: vec![
511 mid_disp_x
512 + x1
513 + (x2 - x1) * diverge_point
514 + Renderer::_offset_opt(offset, o, roughness_gain),
515 mid_disp_y
516 + y1
517 + (y2 - y1) * diverge_point
518 + Renderer::_offset_opt(offset, o, roughness_gain),
519 mid_disp_x
520 + x1
521 + 2.0 * (x2 - x1) * diverge_point
522 + Renderer::_offset_opt(offset, o, roughness_gain),
523 mid_disp_y
524 + y1
525 + 2.0 * (y2 - y1) * diverge_point
526 + Renderer::_offset_opt(offset, o, roughness_gain),
527 x2 + Renderer::_offset_opt(offset, o, roughness_gain),
528 y2 + Renderer::_offset_opt(offset, o, roughness_gain),
529 ],
530 });
531 }
532 ops
533 }
534
535 fn _curve_with_offset(
536 points: &Vec<Point<f64>>,
537 offset: f64,
538 options: &RoughOptions,
539 ) -> Vec<Op> {
540 let mut ps: Vec<Point<f64>> = Vec::new();
541 let mut iter = points.iter();
542
543 let first = iter.next().unwrap();
544 ps.push(Point::new(
545 first.x + Renderer::_offset_opt(offset, options, 1.0),
546 first.y + Renderer::_offset_opt(offset, options, 1.0),
547 ));
548
549 ps.push(Point::new(
550 first.x + Renderer::_offset_opt(offset, options, 1.0),
551 first.y + Renderer::_offset_opt(offset, options, 1.0),
552 ));
553
554 let mut idx: usize = 1;
555 let last_idx = points.len() - 1;
556 for point in iter {
558 ps.push(Point::new(
559 point.x + Renderer::_offset_opt(offset, options, 1.0),
560 point.y + Renderer::_offset_opt(offset, options, 1.0),
561 ));
562
563 if idx == last_idx {
564 ps.push(Point::new(
565 point.x + Renderer::_offset_opt(offset, options, 1.0),
566 point.y + Renderer::_offset_opt(offset, options, 1.0),
567 ));
568 }
569 idx += 1;
570 }
571
572 Renderer::_curve(ps, None, options)
573 }
574
575 fn _curve(
576 points: Vec<Point<f64>>,
577 close_point: Option<Point<f64>>,
578 o: &RoughOptions,
579 ) -> Vec<Op> {
580 let len = points.len();
581 let mut ops: Vec<Op> = Vec::new();
582 if len > 3 {
583 let s = 1.0 - o.curve_tightness;
584 let pt = points.get(1).unwrap();
585 ops.push(Op {
586 op: OpType::Move,
587 data: vec![pt.x, pt.y],
588 });
589
590 let mut i: usize = 1;
591 while (i + 2) < len {
592 let prev = points.get(i - 1).unwrap();
593 let current = points.get(i).unwrap();
594 let next = points.get(i + 1).unwrap();
595 let next_one = points.get(i + 2).unwrap();
596
597 let b0 = Point::new(current.x, current.y);
598 let b1 = Point::new(
599 current.x + (s * next.x - s * prev.x) / 6.0,
600 current.y + (s * next.y - s * prev.y) / 6.0,
601 );
602 let b2 = Point::new(
603 next.x + (s * current.x - s * next_one.x) / 6.0,
604 next.y + (s * current.y - s * next_one.y) / 6.0,
605 );
606 let b3 = Point::new(next.x, next.y);
607
608 ops.push(Op {
609 op: OpType::BCurveTo,
610 data: vec![b1.x, b1.y, b2.x, b2.y, b3.x, b3.y],
611 });
612 i += 1;
613 }
614 if let Some(close_point) = close_point {
615 let ro = o.max_randomness_offset;
616 ops.push(Op {
617 op: OpType::LineTo,
618 data: vec![
619 close_point.x + Renderer::_offset_opt(ro, o, 1.0),
620 close_point.y + Renderer::_offset_opt(ro, o, 1.0),
621 ],
622 });
623 }
624 } else if len == 3 {
625 let mid = points.get(1).unwrap();
626 ops.push(Op {
627 op: OpType::Move,
628 data: vec![mid.x, mid.y],
629 });
630 let last = points.last().unwrap();
631 ops.push(Op {
632 op: OpType::BCurveTo,
633 data: vec![mid.x, mid.y, last.x, last.y, last.x, last.y],
634 });
635 } else if len == 2 {
636 let first = points.get(0).unwrap();
637 let second = points.get(1).unwrap();
638 let mut append = Renderer::_double_line(first.x, first.y, second.x, second.y, o, false);
639 ops.append(append.as_mut());
640 }
641 ops
642 }
643
644 fn _compute_ellipse_points(
645 increment: f64,
646 cx: f64,
647 cy: f64,
648 rx: f64,
649 ry: f64,
650 offset: f64,
651 overlap: f64,
652 o: &RoughOptions,
653 ) -> (Vec<Point<f64>>, Vec<Point<f64>>) {
654 let mut core_points: Vec<Point<f64>> = Vec::new();
655 let mut all_points: Vec<Point<f64>> = Vec::new();
656
657 let rad_offset = Renderer::_offset_opt(0.5, o, 1.0) - PI / 2.0;
658
659 all_points.push(Point::new(
660 Renderer::_offset_opt(offset, o, 1.0) + cx + 0.9 * rx * (rad_offset - increment).cos(),
661 Renderer::_offset_opt(offset, o, 1.0) + cy + 0.9 * ry * (rad_offset - increment).sin(),
662 ));
663
664 let mut angle = rad_offset;
665 while angle < PI * 2.0 + rad_offset - 0.01 {
666 let p = Point::new(
667 Renderer::_offset_opt(offset, o, 1.0) + cx + rx * angle.cos(),
668 Renderer::_offset_opt(offset, o, 1.0) + cy + ry * angle.sin(),
669 );
670 core_points.push(p);
671 all_points.push(p);
672
673 angle = angle + increment;
674 }
675
676 all_points.push(Point::new(
677 Renderer::_offset_opt(offset, o, 1.0)
678 + cx
679 + rx * (rad_offset + PI * 2.0 + overlap * 0.5).cos(),
680 Renderer::_offset_opt(offset, o, 1.0)
681 + cy
682 + ry * (rad_offset + PI * 2.0 + overlap * 0.5).sin(),
683 ));
684
685 all_points.push(Point::new(
686 Renderer::_offset_opt(offset, o, 1.0) + cx + 0.98 * rx * (rad_offset + overlap).cos(),
687 Renderer::_offset_opt(offset, o, 1.0) + cy + 0.98 * ry * (rad_offset + overlap).sin(),
688 ));
689
690 all_points.push(Point::new(
691 Renderer::_offset_opt(offset, o, 1.0)
692 + cx
693 + 0.9 * rx * (rad_offset + overlap * 0.5).cos(),
694 Renderer::_offset_opt(offset, o, 1.0)
695 + cy
696 + 0.9 * ry * (rad_offset + overlap * 0.5).sin(),
697 ));
698
699 (all_points, core_points)
700 }
701
702 fn _arc(
703 increment: f64,
704 cx: f64,
705 cy: f64,
706 rx: f64,
707 ry: f64,
708 strt: f64,
709 stp: f64,
710 offset: f64,
711 o: &RoughOptions,
712 ) -> Vec<Op> {
713 let rad_offset = strt + Renderer::_offset_opt(0.1, o, 1.0);
716 let mut points: Vec<Point<f64>> = Vec::new();
717 points.push(Point::new(
718 Renderer::_offset_opt(offset, o, 1.0) + cx + 0.9 * rx * (rad_offset - increment).cos(),
719 Renderer::_offset_opt(offset, o, 1.0) + cy + 0.9 * ry * (rad_offset - increment).sin(),
720 ));
721
722 let mut angle = rad_offset;
723 while angle <= stp {
724 points.push(Point::new(
725 Renderer::_offset_opt(offset, o, 1.0) + cx + rx * angle.cos(),
726 Renderer::_offset_opt(offset, o, 1.0) + cy + ry * angle.sin(),
727 ));
728 angle = angle + increment;
729 }
730
731 points.push(Point::new(cx + rx * stp.cos(), cy + ry * stp.sin()));
732 points.push(Point::new(cx + rx * stp.cos(), cy + ry * stp.sin()));
733
734 Renderer::_curve(points, None, o)
735 }
736
737 fn _bezier_to(
738 x1: f64,
739 y1: f64,
740 x2: f64,
741 y2: f64,
742 x: f64,
743 y: f64,
744 current: Point<f64>,
745 options: &RoughOptions,
746 ) -> Vec<Op> {
747 let mut ops: Vec<Op> = Vec::new();
748 let ros = vec![
749 if options.max_randomness_offset != 0.0 {
750 options.max_randomness_offset
751 } else {
752 1.0
753 },
754 if options.max_randomness_offset != 0.0 {
755 options.max_randomness_offset + 0.3
756 } else {
757 1.3
758 },
759 ];
760
761 let iterations = if options.disable_multi_stroke { 1 } else { 2 };
762
763 for i in 0..iterations {
764 if i == 0 {
765 ops.push(Op {
766 op: OpType::Move,
767 data: vec![current.x, current.y],
768 });
769 } else {
770 ops.push(Op {
771 op: OpType::Move,
772 data: vec![
773 current.x + Renderer::_offset_opt(ros[0], options, 1.0),
774 current.y + Renderer::_offset_opt(ros[0], options, 1.0),
775 ],
776 });
777 }
778
779 let fp = Point::new(
780 x + Renderer::_offset_opt(ros[i], options, 1.0),
781 y + Renderer::_offset_opt(ros[i], options, 1.0),
782 );
783
784 ops.push(Op {
785 op: OpType::BCurveTo,
786 data: vec![
787 x1 + Renderer::_offset_opt(ros[i], options, 1.0),
788 y1 + Renderer::_offset_opt(ros[i], options, 1.0),
789 x2 + Renderer::_offset_opt(ros[i], options, 1.0),
790 y2 + Renderer::_offset_opt(ros[i], options, 1.0),
791 fp.x,
792 fp.y,
793 ],
794 });
795 }
796 ops
797 }
798}