1use core::f32;
2
3use egui::{Context, Id, Pos2, Rect, Shape, Stroke, Ui, ahash::HashMap, cache::CacheTrait, pos2};
4
5use crate::{InPinId, OutPinId};
6
7const MAX_CURVE_SAMPLES: usize = 100;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
13#[derive(Default)]
14pub enum WireLayer {
15 #[default]
18 BehindNodes,
19
20 AboveNodes,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub enum WireId {
26 Connected { snarl_id: Id, out_pin: OutPinId, in_pin: InPinId },
27 NewInput { snarl_id: Id, in_pin: InPinId },
28 NewOutput { snarl_id: Id, out_pin: OutPinId },
29}
30
31#[derive(Clone, Copy, Debug, PartialEq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
37#[derive(Default)]
38pub enum WireStyle {
39 Line,
41
42 AxisAligned {
45 corner_radius: f32,
47 },
48
49 Bezier3,
51
52 #[default]
54 Bezier5,
55}
56
57pub const fn pick_wire_style(left: WireStyle, right: WireStyle) -> WireStyle {
58 match (left, right) {
59 (WireStyle::Line, _) | (_, WireStyle::Line) => WireStyle::Line,
60 (WireStyle::AxisAligned { corner_radius: a }, WireStyle::AxisAligned { corner_radius: b }) => {
61 WireStyle::AxisAligned { corner_radius: f32::max(a, b) }
62 }
63 (WireStyle::AxisAligned { corner_radius }, _)
64 | (_, WireStyle::AxisAligned { corner_radius }) => WireStyle::AxisAligned { corner_radius },
65 (WireStyle::Bezier3, _) | (_, WireStyle::Bezier3) => WireStyle::Bezier3,
66 (WireStyle::Bezier5, WireStyle::Bezier5) => WireStyle::Bezier5,
67 }
68}
69
70fn adjust_frame_size(
71 mut frame_size: f32,
72 upscale: bool,
73 downscale: bool,
74 from: Pos2,
75 to: Pos2,
76) -> f32 {
77 let length = (from - to).length();
78 if upscale {
79 frame_size = frame_size.max(length / 6.0);
80 }
81 if downscale {
82 frame_size = frame_size.min(length / 6.0);
83 }
84 frame_size
85}
86
87fn wire_bezier_5(frame_size: f32, from: Pos2, to: Pos2) -> [Pos2; 6] {
89 let from_norm_x = frame_size;
90 let from_2 = pos2(from.x + from_norm_x, from.y);
91 let to_norm_x = -from_norm_x;
92 let to_2 = pos2(to.x + to_norm_x, to.y);
93
94 let between = (from_2 - to_2).length();
95
96 if from_2.x <= to_2.x && between >= frame_size * 2.0 {
97 let middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
98 let middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
99
100 [from, from_2, middle_1, middle_2, to_2, to]
101 } else if from_2.x <= to_2.x {
102 let t =
103 (between - (to_2.y - from_2.y).abs()) / frame_size.mul_add(2.0, -(to_2.y - from_2.y).abs());
104
105 let mut middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
106 let mut middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
107
108 if from_2.y >= to_2.y + frame_size {
109 let u = (from_2.y - to_2.y - frame_size) / frame_size;
110
111 let t0_middle_1 =
112 pos2((1.0 - u).mul_add(frame_size, from_2.x), frame_size.mul_add(-u, from_2.y));
113 let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
114
115 middle_1 = t0_middle_1.lerp(middle_1, t);
116 middle_2 = t0_middle_2.lerp(middle_2, t);
117 } else if from_2.y >= to_2.y {
118 let u = (from_2.y - to_2.y) / frame_size;
119
120 let t0_middle_1 =
121 pos2(u.mul_add(frame_size, from_2.x), frame_size.mul_add(1.0 - u, from_2.y));
122 let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
123
124 middle_1 = t0_middle_1.lerp(middle_1, t);
125 middle_2 = t0_middle_2.lerp(middle_2, t);
126 } else if to_2.y >= from_2.y + frame_size {
127 let u = (to_2.y - from_2.y - frame_size) / frame_size;
128
129 let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
130 let t0_middle_2 =
131 pos2((1.0 - u).mul_add(-frame_size, to_2.x), frame_size.mul_add(-u, to_2.y));
132
133 middle_1 = t0_middle_1.lerp(middle_1, t);
134 middle_2 = t0_middle_2.lerp(middle_2, t);
135 } else if to_2.y >= from_2.y {
136 let u = (to_2.y - from_2.y) / frame_size;
137
138 let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
139 let t0_middle_2 = pos2(u.mul_add(-frame_size, to_2.x), frame_size.mul_add(1.0 - u, to_2.y));
140
141 middle_1 = t0_middle_1.lerp(middle_1, t);
142 middle_2 = t0_middle_2.lerp(middle_2, t);
143 } else {
144 unreachable!();
145 }
146
147 [from, from_2, middle_1, middle_2, to_2, to]
148 } else if from_2.y >= frame_size.mul_add(2.0, to_2.y) {
149 let middle_1 = pos2(from_2.x, from_2.y - frame_size);
150 let middle_2 = pos2(to_2.x, to_2.y + frame_size);
151
152 [from, from_2, middle_1, middle_2, to_2, to]
153 } else if from_2.y >= to_2.y + frame_size {
154 let t = (from_2.y - to_2.y - frame_size) / frame_size;
155
156 let middle_1 = pos2((1.0 - t).mul_add(frame_size, from_2.x), frame_size.mul_add(-t, from_2.y));
157 let middle_2 = pos2(to_2.x, to_2.y + frame_size);
158
159 [from, from_2, middle_1, middle_2, to_2, to]
160 } else if from_2.y >= to_2.y {
161 let t = (from_2.y - to_2.y) / frame_size;
162
163 let middle_1 = pos2(t.mul_add(frame_size, from_2.x), frame_size.mul_add(1.0 - t, from_2.y));
164 let middle_2 = pos2(to_2.x, to_2.y + frame_size);
165
166 [from, from_2, middle_1, middle_2, to_2, to]
167 } else if to_2.y >= frame_size.mul_add(2.0, from_2.y) {
168 let middle_1 = pos2(from_2.x, from_2.y + frame_size);
169 let middle_2 = pos2(to_2.x, to_2.y - frame_size);
170
171 [from, from_2, middle_1, middle_2, to_2, to]
172 } else if to_2.y >= from_2.y + frame_size {
173 let t = (to_2.y - from_2.y - frame_size) / frame_size;
174
175 let middle_1 = pos2(from_2.x, from_2.y + frame_size);
176 let middle_2 = pos2((1.0 - t).mul_add(-frame_size, to_2.x), frame_size.mul_add(-t, to_2.y));
177
178 [from, from_2, middle_1, middle_2, to_2, to]
179 } else if to_2.y >= from_2.y {
180 let t = (to_2.y - from_2.y) / frame_size;
181
182 let middle_1 = pos2(from_2.x, from_2.y + frame_size);
183 let middle_2 = pos2(t.mul_add(-frame_size, to_2.x), frame_size.mul_add(1.0 - t, to_2.y));
184
185 [from, from_2, middle_1, middle_2, to_2, to]
186 } else {
187 unreachable!();
188 }
189}
190
191fn wire_bezier_3(frame_size: f32, from: Pos2, to: Pos2) -> [Pos2; 4] {
193 let [a, b, _, _, c, d] = wire_bezier_5(frame_size, from, to);
194 [a, b, c, d]
195}
196
197#[allow(clippy::too_many_arguments)]
198pub fn draw_wire(
199 ui: &Ui,
200 wire: WireId,
201 shapes: &mut Vec<Shape>,
202 frame_size: f32,
203 upscale: bool,
204 downscale: bool,
205 from: Pos2,
206 to: Pos2,
207 mut stroke: Stroke,
208 threshold: f32,
209 style: WireStyle,
210) {
211 if !ui.is_visible() {
212 return;
213 }
214
215 if stroke.width < 1.0 {
216 stroke.color = stroke.color.gamma_multiply(stroke.width);
217 stroke.width = 1.0;
218 }
219
220 let frame_size = adjust_frame_size(frame_size, upscale, downscale, from, to);
221
222 let args = WireArgs { frame_size, from, to, radius: 0.0 };
223
224 match style {
225 WireStyle::Line => {
226 let bb = Rect::from_two_pos(from, to);
227 if ui.is_rect_visible(bb) {
228 shapes.push(Shape::line_segment([from, to], stroke));
229 }
230 }
231 WireStyle::Bezier3 => {
232 draw_bezier_3(ui, wire, args, stroke, threshold, shapes);
233 }
234
235 WireStyle::Bezier5 => {
236 draw_bezier_5(ui, wire, args, stroke, threshold, shapes);
237 }
238
239 WireStyle::AxisAligned { corner_radius } => {
240 let args = WireArgs { radius: corner_radius, ..args };
241 draw_axis_aligned(ui, wire, args, stroke, threshold, shapes);
242 }
243 }
244}
245
246#[allow(clippy::too_many_arguments)]
247pub fn hit_wire(
248 ctx: &Context,
249 wire: WireId,
250 frame_size: f32,
251 upscale: bool,
252 downscale: bool,
253 from: Pos2,
254 to: Pos2,
255 pos: Pos2,
256 hit_threshold: f32,
257 style: WireStyle,
258) -> bool {
259 let frame_size = adjust_frame_size(frame_size, upscale, downscale, from, to);
260
261 let args = WireArgs { frame_size, from, to, radius: 0.0 };
262
263 match style {
264 WireStyle::Line => {
265 let aabb = Rect::from_two_pos(from, to);
266 let aabb_e = aabb.expand(hit_threshold);
267 if !aabb_e.contains(pos) {
268 return false;
269 }
270
271 let a = to - from;
272 let b = pos - from;
273
274 let dot = b.dot(a);
275 let dist2 = b.length_sq() - dot * dot / a.length_sq();
276
277 dist2 < hit_threshold * hit_threshold
278 }
279 WireStyle::Bezier3 => hit_wire_bezier_3(ctx, wire, args, pos, hit_threshold),
280 WireStyle::Bezier5 => hit_wire_bezier_5(ctx, wire, args, pos, hit_threshold),
281 WireStyle::AxisAligned { corner_radius } => {
282 let args = WireArgs { radius: corner_radius, ..args };
283 hit_wire_axis_aligned(ctx, wire, args, pos, hit_threshold)
284 }
285 }
286}
287
288#[inline]
289fn bezier_arc_length_upper_bound(points: &[Pos2]) -> f32 {
290 let mut size = 0.0;
291 for i in 1..points.len() {
292 size += (points[i] - points[i - 1]).length();
293 }
294 size
295}
296
297fn bezier_hit_samples_number(points: &[Pos2], threshold: f32) -> usize {
298 let arc_length = bezier_arc_length_upper_bound(points);
299
300 #[allow(clippy::cast_sign_loss)]
301 #[allow(clippy::cast_possible_truncation)]
302 ((arc_length / threshold).ceil().max(0.0) as usize)
303}
304
305fn bezier_derivative_3(points: &[Pos2; 4]) -> [Pos2; 3] {
306 let [p0, p1, p2, p3] = *points;
307
308 let factor = 3.0;
309
310 [(factor * (p1 - p0)).to_pos2(), (factor * (p2 - p1)).to_pos2(), (factor * (p3 - p2)).to_pos2()]
311}
312
313fn bezier_derivative_5(points: &[Pos2; 6]) -> [Pos2; 5] {
314 let [p0, p1, p2, p3, p4, p5] = *points;
315
316 let factor = 5.0;
317
318 [
319 (factor * (p1 - p0)).to_pos2(),
320 (factor * (p2 - p1)).to_pos2(),
321 (factor * (p3 - p2)).to_pos2(),
322 (factor * (p4 - p3)).to_pos2(),
323 (factor * (p5 - p4)).to_pos2(),
324 ]
325}
326
327fn bezier_draw_samples_number_3(points: &[Pos2; 4], threshold: f32) -> usize {
328 #![allow(clippy::similar_names)]
329 #![allow(clippy::cast_precision_loss)]
330
331 let d = bezier_derivative_3(points);
332
333 lower_bound(2, MAX_CURVE_SAMPLES, |n| {
334 let mut prev = points[0];
335 for i in 1..n {
336 let t = i as f32 / (n - 1) as f32;
337 let next = sample_bezier(points, t);
338
339 let m = t - 0.5 / (n - 1) as f32;
340
341 let mid_line = ((prev.to_vec2() + next.to_vec2()) * 0.5).to_pos2();
343 let mid_curve = sample_bezier(points, m);
344
345 let error_sq = (mid_curve - mid_line).length_sq();
346 if error_sq > threshold * threshold {
347 return false;
348 }
349
350 let mid_line_dx = next.x - prev.x;
352 let mid_line_dy = next.y - prev.y;
353
354 let line_w = f32::hypot(mid_line_dx, mid_line_dy);
355
356 let d_curve = sample_bezier(&d, m);
357 let mid_curve_dx = d_curve.x;
358 let mid_curve_dy = d_curve.y;
359
360 let curve_w = f32::hypot(mid_curve_dx, mid_curve_dy);
361
362 let error = f32::max(
363 (mid_curve_dx / curve_w).mul_add(line_w, -mid_line_dx).abs(),
364 (mid_curve_dy / curve_w).mul_add(line_w, -mid_line_dy).abs(),
365 );
366 if error > threshold * 2.0 {
367 return false;
368 }
369
370 prev = next;
371 }
372
373 true
374 })
375}
376
377fn bezier_draw_samples_number_5(points: &[Pos2; 6], threshold: f32) -> usize {
378 #![allow(clippy::similar_names)]
379 #![allow(clippy::cast_precision_loss)]
380
381 let d = bezier_derivative_5(points);
382
383 lower_bound(2, MAX_CURVE_SAMPLES, |n| {
384 let mut prev = points[0];
385 for i in 1..n {
386 let t = i as f32 / (n - 1) as f32;
387 let next = sample_bezier(points, t);
388
389 let m = t - 0.5 / (n - 1) as f32;
390
391 let mid_line = ((prev.to_vec2() + next.to_vec2()) * 0.5).to_pos2();
393 let mid_curve = sample_bezier(points, m);
394
395 let error_sq = (mid_curve - mid_line).length_sq();
396 if error_sq > threshold * threshold {
397 return false;
398 }
399
400 let mid_line_dx = next.x - prev.x;
402 let mid_line_dy = next.y - prev.y;
403
404 let line_w = f32::hypot(mid_line_dx, mid_line_dy);
405
406 let d_curve = sample_bezier(&d, m);
407 let mid_curve_dx = d_curve.x;
408 let mid_curve_dy = d_curve.y;
409
410 let curve_w = f32::hypot(mid_curve_dx, mid_curve_dy);
411
412 let error = f32::max(
413 (mid_curve_dx / curve_w).mul_add(line_w, -mid_line_dx).abs(),
414 (mid_curve_dy / curve_w).mul_add(line_w, -mid_line_dy).abs(),
415 );
416 if error > threshold * 2.0 {
417 return false;
418 }
419
420 prev = next;
421 }
422
423 true
424 })
425}
426
427#[derive(Clone, Copy, PartialEq)]
428struct WireArgs {
429 frame_size: f32,
430 from: Pos2,
431 to: Pos2,
432 radius: f32,
433}
434
435impl Default for WireArgs {
436 fn default() -> Self {
437 WireArgs { frame_size: 0.0, from: Pos2::ZERO, to: Pos2::ZERO, radius: 0.0 }
438 }
439}
440
441struct WireCache3 {
442 generation: u32,
443 args: WireArgs,
444 aabb: Rect,
445 points: [Pos2; 4],
446 threshold: f32,
447 line: Vec<Pos2>,
448}
449
450impl Default for WireCache3 {
451 fn default() -> Self {
452 WireCache3 {
453 generation: 0,
454 args: WireArgs::default(),
455 aabb: Rect::NOTHING,
456 points: [Pos2::ZERO; 4],
457 threshold: 0.0,
458 line: Vec::new(),
459 }
460 }
461}
462
463impl WireCache3 {
464 fn line(&mut self, threshold: f32) -> Vec<Pos2> {
465 #[allow(clippy::float_cmp)]
466 if !self.line.is_empty() && self.threshold == threshold {
467 return self.line.clone();
468 }
469
470 let samples = bezier_draw_samples_number_3(&self.points, threshold);
471
472 let line = (0..samples)
473 .map(|i| {
474 #[allow(clippy::cast_precision_loss)]
475 let t = i as f32 / (samples - 1) as f32;
476 sample_bezier(&self.points, t)
477 })
478 .collect::<Vec<Pos2>>();
479
480 self.threshold = threshold;
481 self.line.clone_from(&line);
482
483 line
484 }
485}
486
487struct WireCache5 {
488 generation: u32,
489 args: WireArgs,
490 aabb: Rect,
491 points: [Pos2; 6],
492 threshold: f32,
493 line: Vec<Pos2>,
494}
495
496impl Default for WireCache5 {
497 fn default() -> Self {
498 Self {
499 generation: 0,
500 args: WireArgs::default(),
501 aabb: Rect::NOTHING,
502 points: [Pos2::ZERO; 6],
503 threshold: 0.0,
504 line: Vec::new(),
505 }
506 }
507}
508
509impl WireCache5 {
510 fn line(&mut self, threshold: f32) -> Vec<Pos2> {
511 #[allow(clippy::float_cmp)]
512 if !self.line.is_empty() && self.threshold == threshold {
513 return self.line.clone();
514 }
515
516 let samples = bezier_draw_samples_number_5(&self.points, threshold);
517
518 let line = (0..samples)
519 .map(|i| {
520 #[allow(clippy::cast_precision_loss)]
521 let t = i as f32 / (samples - 1) as f32;
522 sample_bezier(&self.points, t)
523 })
524 .collect::<Vec<Pos2>>();
525
526 self.threshold = threshold;
527 self.line.clone_from(&line);
528
529 line
530 }
531}
532
533#[derive(Default)]
534struct WireCacheAA {
535 generation: u32,
536 args: WireArgs,
537 aawire: AxisAlignedWire,
538 threshold: f32,
539 line: Vec<Pos2>,
540}
541
542impl WireCacheAA {
543 fn line(&mut self, threshold: f32) -> Vec<Pos2> {
544 #[allow(clippy::float_cmp)]
545 if !self.line.is_empty() && self.threshold == threshold {
546 return self.line.clone();
547 }
548
549 let mut line = Vec::new();
550
551 for i in 0..self.aawire.turns {
552 line.push(self.aawire.segments[i].0);
559 line.push(self.aawire.segments[i].1);
560
561 if self.aawire.turn_radii[i] > 0.0 {
562 let turn = self.aawire.turn_centers[i];
563 let samples = turn_samples_number(self.aawire.turn_radii[i], self.threshold);
564
565 let start = self.aawire.segments[i].1;
566 let end = self.aawire.segments[i + 1].0;
567
568 let sin_x = end.x - turn.x;
569 let cos_x = start.x - turn.x;
570
571 let sin_y = end.y - turn.y;
572 let cos_y = start.y - turn.y;
573
574 for j in 1..samples {
575 #[allow(clippy::cast_precision_loss)]
576 let a = std::f32::consts::FRAC_PI_2 * (j as f32 / samples as f32);
577
578 let (sin_a, cos_a) = a.sin_cos();
579
580 let point: Pos2 = pos2(
581 cos_x.mul_add(cos_a, sin_x.mul_add(sin_a, turn.x)),
582 cos_y.mul_add(cos_a, sin_y.mul_add(sin_a, turn.y)),
583 );
584 line.push(point);
585 }
586 }
587 }
588
589 line.push(self.aawire.segments[self.aawire.turns].0);
590 line.push(self.aawire.segments[self.aawire.turns].1);
591
592 self.threshold = threshold;
593 self.line.clone_from(&line);
594
595 line
596 }
597}
598
599#[derive(Default)]
600struct WiresCache {
601 generation: u32,
602 bezier_3: HashMap<WireId, WireCache3>,
603 bezier_5: HashMap<WireId, WireCache5>,
604 axis_aligned: HashMap<WireId, WireCacheAA>,
605}
606
607impl CacheTrait for WiresCache {
608 fn update(&mut self) {
609 self.bezier_3.retain(|_, cache| cache.generation == self.generation);
610 self.bezier_5.retain(|_, cache| cache.generation == self.generation);
611 self.axis_aligned.retain(|_, cache| cache.generation == self.generation);
612
613 self.generation = self.generation.wrapping_add(1);
614 }
615
616 fn len(&self) -> usize {
617 self.bezier_3.len() + self.bezier_5.len() + self.axis_aligned.len()
618 }
619
620 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
621 self
622 }
623}
624
625impl WiresCache {
626 pub fn get_3(&mut self, wire: WireId, args: WireArgs) -> &mut WireCache3 {
627 let cached = self.bezier_3.entry(wire).or_default();
628
629 cached.generation = self.generation;
630
631 if cached.args == args {
632 return cached;
633 }
634
635 let points = wire_bezier_3(args.frame_size, args.from, args.to);
636 let aabb = Rect::from_points(&points);
637
638 cached.args = args;
639 cached.points = points;
640 cached.aabb = aabb;
641 cached.line.clear();
642
643 cached
644 }
645
646 pub fn get_5(&mut self, wire: WireId, args: WireArgs) -> &mut WireCache5 {
647 let cached = self.bezier_5.entry(wire).or_default();
648
649 cached.generation = self.generation;
650
651 if cached.args == args {
652 return cached;
653 }
654
655 let points = wire_bezier_5(args.frame_size, args.from, args.to);
656 let aabb = Rect::from_points(&points);
657
658 cached.args = args;
659 cached.points = points;
660 cached.aabb = aabb;
661 cached.line.clear();
662
663 cached
664 }
665
666 #[allow(clippy::too_many_arguments)]
667 pub fn get_aa(&mut self, wire: WireId, args: WireArgs) -> &mut WireCacheAA {
668 let cached = self.axis_aligned.entry(wire).or_default();
669
670 cached.generation = self.generation;
671
672 if cached.args == args {
673 return cached;
674 }
675
676 let aawire = wire_axis_aligned(args.radius, args.frame_size, args.from, args.to);
677
678 cached.args = args;
679 cached.aawire = aawire;
680 cached.line.clear();
681
682 cached
683 }
684}
685
686#[inline(never)]
687fn draw_bezier_3(
688 ui: &Ui,
689 wire: WireId,
690 args: WireArgs,
691 stroke: Stroke,
692 threshold: f32,
693 shapes: &mut Vec<Shape>,
694) {
695 debug_assert!(ui.is_visible(), "Must be checked earlier");
696
697 let clip_rect = ui.clip_rect();
698
699 ui.memory_mut(|m| {
700 let cached = m.caches.cache::<WiresCache>().get_3(wire, args);
701
702 if cached.aabb.intersects(clip_rect) {
703 shapes.push(Shape::line(cached.line(threshold), stroke));
704 }
705 });
706
707 }
727
728fn draw_bezier_5(
729 ui: &Ui,
730 wire: WireId,
731 args: WireArgs,
732 stroke: Stroke,
733 threshold: f32,
734 shapes: &mut Vec<Shape>,
735) {
736 debug_assert!(ui.is_visible(), "Must be checked earlier");
737
738 let clip_rect = ui.clip_rect();
739
740 ui.memory_mut(|m| {
741 let cached = m.caches.cache::<WiresCache>().get_5(wire, args);
742
743 if cached.aabb.intersects(clip_rect) {
744 shapes.push(Shape::line(cached.line(threshold), stroke));
745 }
746 });
747
748 }
768
769fn sample_bezier(points: &[Pos2], t: f32) -> Pos2 {
771 match *points {
772 [] => unimplemented!(),
773 [p0] => p0,
774 [p0, p1] => p0.lerp(p1, t),
775 [p0, p1, p2] => {
776 let p0_0 = p0;
777 let p1_0 = p1;
778 let p2_0 = p2;
779
780 let p0_1 = p0_0.lerp(p1_0, t);
781 let p1_1 = p1_0.lerp(p2_0, t);
782
783 p0_1.lerp(p1_1, t)
784 }
785 [p0, p1, p2, p3] => {
786 let p0_0 = p0;
787 let p1_0 = p1;
788 let p2_0 = p2;
789 let p3_0 = p3;
790
791 let p0_1 = p0_0.lerp(p1_0, t);
792 let p1_1 = p1_0.lerp(p2_0, t);
793 let p2_1 = p2_0.lerp(p3_0, t);
794
795 sample_bezier(&[p0_1, p1_1, p2_1], t)
796 }
797 [p0, p1, p2, p3, p4] => {
798 let p0_0 = p0;
799 let p1_0 = p1;
800 let p2_0 = p2;
801 let p3_0 = p3;
802 let p4_0 = p4;
803
804 let p0_1 = p0_0.lerp(p1_0, t);
805 let p1_1 = p1_0.lerp(p2_0, t);
806 let p2_1 = p2_0.lerp(p3_0, t);
807 let p3_1 = p3_0.lerp(p4_0, t);
808
809 sample_bezier(&[p0_1, p1_1, p2_1, p3_1], t)
810 }
811 [p0, p1, p2, p3, p4, p5] => {
812 let p0_0 = p0;
813 let p1_0 = p1;
814 let p2_0 = p2;
815 let p3_0 = p3;
816 let p4_0 = p4;
817 let p5_0 = p5;
818
819 let p0_1 = p0_0.lerp(p1_0, t);
820 let p1_1 = p1_0.lerp(p2_0, t);
821 let p2_1 = p2_0.lerp(p3_0, t);
822 let p3_1 = p3_0.lerp(p4_0, t);
823 let p4_1 = p4_0.lerp(p5_0, t);
824
825 sample_bezier(&[p0_1, p1_1, p2_1, p3_1, p4_1], t)
826 }
827 _ => unimplemented!(),
828 }
829}
830
831fn split_bezier_3(points: &[Pos2; 4], t: f32) -> [[Pos2; 4]; 2] {
832 let [p0, p1, p2, p3] = *points;
833
834 let p0_0 = p0;
835 let p1_0 = p1;
836 let p2_0 = p2;
837 let p3_0 = p3;
838
839 let p0_1 = p0_0.lerp(p1_0, t);
840 let p1_1 = p1_0.lerp(p2_0, t);
841 let p2_1 = p2_0.lerp(p3_0, t);
842
843 let p0_2 = p0_1.lerp(p1_1, t);
844 let p1_2 = p1_1.lerp(p2_1, t);
845
846 let p0_3 = p0_2.lerp(p1_2, t);
847
848 [[p0_0, p0_1, p0_2, p0_3], [p0_3, p1_2, p2_1, p3_0]]
849}
850
851fn hit_wire_bezier_3(
852 ctx: &Context,
853 wire: WireId,
854 args: WireArgs,
855 pos: Pos2,
856 hit_threshold: f32,
857) -> bool {
858 let (aabb, points) = ctx.memory_mut(|m| {
859 let cache = m.caches.cache::<WiresCache>().get_3(wire, args);
860
861 (cache.aabb, cache.points)
862 });
863
864 let aabb_e = aabb.expand(hit_threshold);
865 if !aabb_e.contains(pos) {
866 return false;
867 }
868
869 hit_bezier_3(&points, pos, hit_threshold)
870}
871
872fn hit_bezier_3(points: &[Pos2; 4], pos: Pos2, hit_threshold: f32) -> bool {
873 let samples = bezier_hit_samples_number(points, hit_threshold);
874 if samples > 8 {
875 let [points1, points2] = split_bezier_3(points, 0.5);
876
877 let aabb_e = Rect::from_points(&points1).expand(hit_threshold);
878 if aabb_e.contains(pos) && hit_bezier_3(&points1, pos, hit_threshold) {
879 return true;
880 }
881 let aabb_e = Rect::from_points(&points2).expand(hit_threshold);
882 if aabb_e.contains(pos) && hit_bezier_3(&points2, pos, hit_threshold) {
883 return true;
884 }
885 return false;
886 }
887
888 let threshold_sq = hit_threshold * hit_threshold;
889
890 for i in 0..samples {
891 #[allow(clippy::cast_precision_loss)]
892 let t = i as f32 / (samples - 1) as f32;
893 let p = sample_bezier(points, t);
894 if p.distance_sq(pos) <= threshold_sq {
895 return true;
896 }
897 }
898
899 false
900}
901
902fn split_bezier_5(points: &[Pos2; 6], t: f32) -> [[Pos2; 6]; 2] {
903 let [p0, p1, p2, p3, p4, p5] = *points;
904
905 let p0_0 = p0;
906 let p1_0 = p1;
907 let p2_0 = p2;
908 let p3_0 = p3;
909 let p4_0 = p4;
910 let p5_0 = p5;
911
912 let p0_1 = p0_0.lerp(p1_0, t);
913 let p1_1 = p1_0.lerp(p2_0, t);
914 let p2_1 = p2_0.lerp(p3_0, t);
915 let p3_1 = p3_0.lerp(p4_0, t);
916 let p4_1 = p4_0.lerp(p5_0, t);
917
918 let p0_2 = p0_1.lerp(p1_1, t);
919 let p1_2 = p1_1.lerp(p2_1, t);
920 let p2_2 = p2_1.lerp(p3_1, t);
921 let p3_2 = p3_1.lerp(p4_1, t);
922
923 let p0_3 = p0_2.lerp(p1_2, t);
924 let p1_3 = p1_2.lerp(p2_2, t);
925 let p2_3 = p2_2.lerp(p3_2, t);
926
927 let p0_4 = p0_3.lerp(p1_3, t);
928 let p1_4 = p1_3.lerp(p2_3, t);
929
930 let p0_5 = p0_4.lerp(p1_4, t);
931
932 [[p0_0, p0_1, p0_2, p0_3, p0_4, p0_5], [p0_5, p1_4, p2_3, p3_2, p4_1, p5_0]]
933}
934
935fn hit_wire_bezier_5(
936 ctx: &Context,
937 wire: WireId,
938 args: WireArgs,
939 pos: Pos2,
940 hit_threshold: f32,
941) -> bool {
942 let (aabb, points) = ctx.memory_mut(|m| {
943 let cache = m.caches.cache::<WiresCache>().get_5(wire, args);
944
945 (cache.aabb, cache.points)
946 });
947
948 let aabb_e = aabb.expand(hit_threshold);
949 if !aabb_e.contains(pos) {
950 return false;
951 }
952
953 hit_bezier_5(&points, pos, hit_threshold)
954}
955
956fn hit_bezier_5(points: &[Pos2; 6], pos: Pos2, hit_threshold: f32) -> bool {
957 let samples = bezier_hit_samples_number(points, hit_threshold);
958 if samples > 16 {
959 let [points1, points2] = split_bezier_5(points, 0.5);
960 let aabb_e = Rect::from_points(&points1).expand(hit_threshold);
961 if aabb_e.contains(pos) && hit_bezier_5(&points1, pos, hit_threshold) {
962 return true;
963 }
964 let aabb_e = Rect::from_points(&points2).expand(hit_threshold);
965 if aabb_e.contains(pos) && hit_bezier_5(&points2, pos, hit_threshold) {
966 return true;
967 }
968 return false;
969 }
970
971 let threshold_sq = hit_threshold * hit_threshold;
972
973 for i in 0..samples {
974 #[allow(clippy::cast_precision_loss)]
975 let t = i as f32 / (samples - 1) as f32;
976 let p = sample_bezier(points, t);
977
978 if p.distance_sq(pos) <= threshold_sq {
979 return true;
980 }
981 }
982
983 false
984}
985
986#[derive(Clone, Copy, PartialEq)]
987struct AxisAlignedWire {
988 aabb: Rect,
989 turns: usize,
990 segments: [(Pos2, Pos2); 5],
991 turn_centers: [Pos2; 4],
992 turn_radii: [f32; 4],
993}
994
995impl Default for AxisAlignedWire {
996 #[inline]
997 fn default() -> Self {
998 Self {
999 aabb: Rect::NOTHING,
1000 turns: 0,
1001 segments: [(Pos2::ZERO, Pos2::ZERO); 5],
1002 turn_centers: [Pos2::ZERO; 4],
1003 turn_radii: [0.0; 4],
1004 }
1005 }
1006}
1007
1008#[allow(clippy::too_many_lines)]
1009fn wire_axis_aligned(corner_radius: f32, frame_size: f32, from: Pos2, to: Pos2) -> AxisAlignedWire {
1010 let corner_radius = corner_radius.max(0.0);
1011
1012 let half_height = f32::abs(from.y - to.y) / 2.0;
1013 let max_radius = (half_height / 2.0).min(corner_radius);
1014
1015 let frame_size = frame_size.max(max_radius * 2.0);
1016
1017 let zero_segment = (Pos2::ZERO, Pos2::ZERO);
1018
1019 if from.x + frame_size <= to.x - frame_size {
1020 if f32::abs(from.y - to.y) < 1.0 {
1021 AxisAlignedWire {
1023 aabb: Rect::from_two_pos(from, to),
1024 segments: [(from, to), zero_segment, zero_segment, zero_segment, zero_segment],
1025 turns: 0,
1026 turn_centers: [Pos2::ZERO; 4],
1027 turn_radii: [f32::NAN; 4],
1028 }
1029 } else {
1030 let mid_x = f32::midpoint(from.x, to.x);
1032 let half_width = (to.x - from.x) / 2.0;
1033
1034 let turn_radius = max_radius.min(half_width);
1035
1036 let turn_vert_len = if from.y < to.y { turn_radius } else { -turn_radius };
1037
1038 let segments = [
1039 (from, pos2(mid_x - turn_radius, from.y)),
1040 (pos2(mid_x, from.y + turn_vert_len), pos2(mid_x, to.y - turn_vert_len)),
1041 (pos2(mid_x + turn_radius, to.y), to),
1042 zero_segment,
1043 zero_segment,
1044 ];
1045
1046 let turn_centers = [
1047 pos2(mid_x - turn_radius, from.y + turn_vert_len),
1048 pos2(mid_x + turn_radius, to.y - turn_vert_len),
1049 Pos2::ZERO,
1050 Pos2::ZERO,
1051 ];
1052
1053 let turn_radii = [turn_radius, turn_radius, f32::NAN, f32::NAN];
1054
1055 AxisAlignedWire {
1056 aabb: Rect::from_two_pos(from, to),
1057 turns: 2,
1058 segments,
1059 turn_centers,
1060 turn_radii,
1061 }
1062 }
1063 } else {
1064 let mid = f32::midpoint(from.y, to.y);
1066
1067 let right = from.x + frame_size;
1068 let left = to.x - frame_size;
1069
1070 let half_width = f32::abs(right - left) / 2.0;
1071
1072 let ends_turn_radius = max_radius;
1073 let middle_turn_radius = max_radius.min(half_width);
1074
1075 let ends_turn_vert_len = if from.y < to.y { ends_turn_radius } else { -ends_turn_radius };
1076
1077 let middle_turn_vert_len = if from.y < to.y { middle_turn_radius } else { -middle_turn_radius };
1078
1079 let segments = [
1080 (from, pos2(right - ends_turn_radius, from.y)),
1081 (pos2(right, from.y + ends_turn_vert_len), pos2(right, mid - middle_turn_vert_len)),
1082 (pos2(right - middle_turn_radius, mid), pos2(left + middle_turn_radius, mid)),
1083 (pos2(left, mid + middle_turn_vert_len), pos2(left, to.y - ends_turn_vert_len)),
1084 (pos2(left + ends_turn_radius, to.y), to),
1085 ];
1086
1087 let turn_centers = [
1088 pos2(right - ends_turn_radius, from.y + ends_turn_vert_len),
1089 pos2(right - middle_turn_radius, mid - middle_turn_vert_len),
1090 pos2(left + middle_turn_radius, mid + middle_turn_vert_len),
1091 pos2(left + ends_turn_radius, to.y - ends_turn_vert_len),
1092 ];
1093
1094 let turn_radii = [ends_turn_radius, middle_turn_radius, middle_turn_radius, ends_turn_radius];
1095
1096 AxisAlignedWire {
1097 aabb: Rect::from_min_max(
1098 pos2(f32::min(left, from.x), f32::min(from.y, to.y)),
1099 pos2(f32::max(right, to.x), f32::max(from.y, to.y)),
1100 ),
1101 turns: 4,
1102 segments,
1103 turn_centers,
1104 turn_radii,
1105 }
1106 }
1107}
1108
1109fn hit_wire_axis_aligned(
1110 ctx: &Context,
1111 wire: WireId,
1112 args: WireArgs,
1113 pos: Pos2,
1114 hit_threshold: f32,
1115) -> bool {
1116 let aawire = ctx.memory_mut(|m| {
1117 let cache = m.caches.cache::<WiresCache>().get_aa(wire, args);
1118
1119 cache.aawire
1120 });
1121
1122 if !aawire.aabb.expand(hit_threshold).contains(pos) {
1124 return false;
1125 }
1126
1127 for i in 0..aawire.turns + 1 {
1130 let (start, end) = aawire.segments[i];
1131
1132 if Rect::from_two_pos(start, end).expand(hit_threshold).contains(pos) {
1135 return true;
1136 }
1137 }
1138
1139 for i in 0..aawire.turns {
1141 if aawire.turn_radii[i] > 0.0 {
1142 let turn = aawire.turn_centers[i];
1143 let turn_aabb = Rect::from_two_pos(aawire.segments[i].1, aawire.segments[i + 1].0);
1144 if !turn_aabb.contains(pos) {
1145 continue;
1146 }
1147
1148 let dist2 = (turn - pos).length_sq();
1150 let min = aawire.turn_radii[i] - hit_threshold;
1151 let max = aawire.turn_radii[i] + hit_threshold;
1152
1153 if dist2 <= max * max && dist2 >= min * min {
1154 return true;
1155 }
1156 }
1157 }
1158
1159 false
1160}
1161
1162fn turn_samples_number(radius: f32, threshold: f32) -> usize {
1163 #![allow(clippy::cast_sign_loss)]
1164 #![allow(clippy::cast_possible_truncation)]
1165 #![allow(clippy::cast_precision_loss)]
1166
1167 if threshold / radius >= 1.0 {
1168 return 2;
1169 }
1170
1171 let a: f32 = (1.0 - threshold / radius).acos();
1172 let samples =
1173 (std::f32::consts::PI / (4.0 * a) + 1.0).min(MAX_CURVE_SAMPLES as f32).ceil() as usize;
1174
1175 samples.clamp(2, MAX_CURVE_SAMPLES)
1176}
1177
1178#[allow(clippy::too_many_arguments)]
1179fn draw_axis_aligned(
1180 ui: &Ui,
1181 wire: WireId,
1182 args: WireArgs,
1183 stroke: Stroke,
1184 threshold: f32,
1185 shapes: &mut Vec<Shape>,
1186) {
1187 debug_assert!(ui.is_visible(), "Must be checked earlier");
1188
1189 let clip_rect = ui.clip_rect();
1190 ui.memory_mut(|m| {
1191 let cached = m.caches.cache::<WiresCache>().get_aa(wire, args);
1192
1193 if cached.aawire.aabb.intersects(clip_rect) {
1194 shapes.push(Shape::line(cached.line(threshold), stroke));
1195 }
1196 });
1197}
1198
1199fn lower_bound(min: usize, max: usize, f: impl Fn(usize) -> bool) -> usize {
1207 #![allow(clippy::similar_names)]
1208
1209 let mut min = min;
1210 let mut max = max;
1211
1212 while min < max {
1213 let mid = usize::midpoint(min, max);
1214 if f(mid) {
1215 max = mid;
1216 } else {
1217 min = mid + 1;
1218 }
1219 }
1220
1221 max
1222
1223 }