1use std::{mem::size_of, ops::Range, rc::Rc};
2
3use cranpose_core::{NodeId, collections::map::HashSet};
4use cranpose_ui::{
5 GraphicsLayer, ModifierNodeSlices, Point, Rect, RenderEffect, RoundedCornerShape,
6 TextLayoutOptions, TextOverflow, TextStyle,
7 text::{AnnotatedString, RenderString},
8};
9use cranpose_ui_graphics::{
10 BlendMode, ColorFilter, CommandRecording, DrawPrimitive, RecordingSummary, ShadowPrimitive,
11};
12
13use crate::{raster_cache::LayerRasterCacheHashes, style_shared::DrawPlacement};
14
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct ProjectiveTransform {
17 matrix: [[f32; 3]; 3],
18}
19
20impl ProjectiveTransform {
21 pub const fn identity() -> Self {
22 Self {
23 matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
24 }
25 }
26
27 pub fn translation(tx: f32, ty: f32) -> Self {
28 Self {
29 matrix: [[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]],
30 }
31 }
32
33 pub fn uniform_scale(scale: f32) -> Self {
36 Self {
37 matrix: [[scale, 0.0, 0.0], [0.0, scale, 0.0], [0.0, 0.0, 1.0]],
38 }
39 }
40
41 pub fn from_rect_to_quad(rect: Rect, quad: [[f32; 2]; 4]) -> Self {
42 if rect.width.abs() <= f32::EPSILON || rect.height.abs() <= f32::EPSILON {
43 return Self::translation(quad[0][0], quad[0][1]);
44 }
45
46 if let Some(axis_aligned) = axis_aligned_rect_from_quad(quad) {
47 let scale_x = axis_aligned.width / rect.width;
48 let scale_y = axis_aligned.height / rect.height;
49 return Self {
50 matrix: [
51 [scale_x, 0.0, axis_aligned.x - rect.x * scale_x],
52 [0.0, scale_y, axis_aligned.y - rect.y * scale_y],
53 [0.0, 0.0, 1.0],
54 ],
55 };
56 }
57
58 let source = [
59 [rect.x, rect.y],
60 [rect.x + rect.width, rect.y],
61 [rect.x, rect.y + rect.height],
62 [rect.x + rect.width, rect.y + rect.height],
63 ];
64 let Some(coefficients) = solve_homography(source, quad) else {
65 return Self::identity();
66 };
67
68 Self {
69 matrix: [
70 [coefficients[0], coefficients[1], coefficients[2]],
71 [coefficients[3], coefficients[4], coefficients[5]],
72 [coefficients[6], coefficients[7], 1.0],
73 ],
74 }
75 }
76
77 pub(crate) fn from_homogeneous(matrix: [[f32; 3]; 3]) -> Self {
81 let w = matrix[2][2];
82 if w == 1.0 || w.abs() <= f32::EPSILON {
83 return Self { matrix };
84 }
85 Self {
86 matrix: matrix.map(|row| row.map(|value| value / w)),
87 }
88 }
89
90 pub fn then(self, next: Self) -> Self {
92 Self {
93 matrix: multiply_matrices(next.matrix, self.matrix),
94 }
95 }
96
97 pub fn inverse(self) -> Option<Self> {
98 let m = self.matrix;
99 let a = m[0][0];
100 let b = m[0][1];
101 let c = m[0][2];
102 let d = m[1][0];
103 let e = m[1][1];
104 let f = m[1][2];
105 let g = m[2][0];
106 let h = m[2][1];
107 let i = m[2][2];
108
109 let cofactor00 = e * i - f * h;
110 let cofactor01 = -(d * i - f * g);
111 let cofactor02 = d * h - e * g;
112 let cofactor10 = -(b * i - c * h);
113 let cofactor11 = a * i - c * g;
114 let cofactor12 = -(a * h - b * g);
115 let cofactor20 = b * f - c * e;
116 let cofactor21 = -(a * f - c * d);
117 let cofactor22 = a * e - b * d;
118
119 let determinant = a * cofactor00 + b * cofactor01 + c * cofactor02;
120 if determinant.abs() <= f32::EPSILON {
121 return None;
122 }
123 let inverse_determinant = 1.0 / determinant;
124
125 Some(Self {
126 matrix: [
127 [
128 cofactor00 * inverse_determinant,
129 cofactor10 * inverse_determinant,
130 cofactor20 * inverse_determinant,
131 ],
132 [
133 cofactor01 * inverse_determinant,
134 cofactor11 * inverse_determinant,
135 cofactor21 * inverse_determinant,
136 ],
137 [
138 cofactor02 * inverse_determinant,
139 cofactor12 * inverse_determinant,
140 cofactor22 * inverse_determinant,
141 ],
142 ],
143 })
144 }
145
146 pub fn matrix(self) -> [[f32; 3]; 3] {
147 self.matrix
148 }
149
150 pub fn map_point(self, point: Point) -> Point {
151 let x = point.x;
152 let y = point.y;
153 let w = self.matrix[2][0] * x + self.matrix[2][1] * y + self.matrix[2][2];
154 let safe_w = if w.abs() <= f32::EPSILON { 1.0 } else { w };
155
156 Point {
157 x: (self.matrix[0][0] * x + self.matrix[0][1] * y + self.matrix[0][2]) / safe_w,
158 y: (self.matrix[1][0] * x + self.matrix[1][1] * y + self.matrix[1][2]) / safe_w,
159 }
160 }
161
162 pub fn map_rect(self, rect: Rect) -> [[f32; 2]; 4] {
163 [
164 self.map_point(Point {
165 x: rect.x,
166 y: rect.y,
167 }),
168 self.map_point(Point {
169 x: rect.x + rect.width,
170 y: rect.y,
171 }),
172 self.map_point(Point {
173 x: rect.x,
174 y: rect.y + rect.height,
175 }),
176 self.map_point(Point {
177 x: rect.x + rect.width,
178 y: rect.y + rect.height,
179 }),
180 ]
181 .map(|point| [point.x, point.y])
182 }
183
184 pub fn bounds_for_rect(self, rect: Rect) -> Rect {
185 quad_bounds(self.map_rect(rect))
186 }
187}
188
189fn axis_aligned_rect_from_quad(quad: [[f32; 2]; 4]) -> Option<Rect> {
190 let top_left = quad[0];
191 let top_right = quad[1];
192 let bottom_left = quad[2];
193 let bottom_right = quad[3];
194 let x_epsilon = 1e-4;
195 let y_epsilon = 1e-4;
196
197 if (top_left[1] - top_right[1]).abs() > y_epsilon
198 || (bottom_left[1] - bottom_right[1]).abs() > y_epsilon
199 || (top_left[0] - bottom_left[0]).abs() > x_epsilon
200 || (top_right[0] - bottom_right[0]).abs() > x_epsilon
201 {
202 return None;
203 }
204
205 Some(Rect {
206 x: top_left[0],
207 y: top_left[1],
208 width: top_right[0] - top_left[0],
209 height: bottom_left[1] - top_left[1],
210 })
211}
212
213impl Default for ProjectiveTransform {
214 fn default() -> Self {
215 Self::identity()
216 }
217}
218
219#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
220pub struct IsolationReasons {
221 pub explicit_offscreen: bool,
222 pub shape_clip: bool,
223 pub effect: bool,
224 pub backdrop: bool,
225 pub group_opacity: bool,
226 pub blend_mode: bool,
227}
228
229impl IsolationReasons {
230 pub fn has_any(self) -> bool {
231 self.explicit_offscreen
232 || self.shape_clip
233 || self.effect
234 || self.backdrop
235 || self.group_opacity
236 || self.blend_mode
237 }
238}
239
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
241pub enum CachePolicy {
242 #[default]
243 None,
244 Auto,
245}
246
247#[derive(Clone)]
248pub struct HitTestNode {
249 pub shape: Option<RoundedCornerShape>,
250 pub handlers: Rc<ModifierNodeSlices>,
255}
256
257#[derive(Clone, Debug, PartialEq)]
258pub struct DrawPrimitiveNode {
259 pub primitive: DrawPrimitive,
260 pub clip: Option<Rect>,
261}
262
263#[derive(Clone, Debug, PartialEq)]
264pub struct TextPrimitiveNode {
265 pub node_id: NodeId,
266 pub rect: Rect,
267 pub text: Rc<AnnotatedString>,
270 pub render_text: std::sync::Arc<RenderString>,
273 pub text_style: std::sync::Arc<TextStyle>,
276 pub font_size: f32,
277 pub layout_options: TextLayoutOptions,
278 pub clip: Option<Rect>,
279}
280
281impl TextPrimitiveNode {
282 fn draws_within(&self, bounds: Rect) -> bool {
285 self.text_style.span_style.shadow.is_none()
286 && self
287 .text
288 .span_styles
289 .iter()
290 .all(|span| span.item.shadow.is_none())
291 && !matches!(self.layout_options.overflow, TextOverflow::Visible)
292 && rect_within(self.rect, bounds)
293 }
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq)]
297pub enum PrimitivePhase {
298 BeforeChildren,
299 AfterChildren,
300}
301
302#[derive(Clone, Debug, PartialEq)]
303pub enum PrimitiveNode {
304 Draw(Box<DrawPrimitiveNode>),
305 Text(Box<TextPrimitiveNode>),
306}
307
308#[derive(Clone, Debug, PartialEq)]
309pub struct PrimitiveEntry {
310 pub phase: PrimitivePhase,
311 pub node: PrimitiveNode,
312}
313
314#[derive(Clone)]
315pub struct LayerNode {
316 pub node_id: Option<NodeId>,
317 pub wraps: Option<NodeId>,
322 pub local_bounds: Rect,
325 pub node_bounds: Option<Rect>,
330 pub transform_to_parent: ProjectiveTransform,
331 pub content_offset: Point,
332 pub motion_context_animated: bool,
333 pub translated_content_context: bool,
334 pub translated_content_offset: Point,
335 pub origin_in_parent: Point,
340 pub graphics_layer: GraphicsLayer,
341 pub clip_to_bounds: bool,
342 pub shadow_clip: Option<Rect>,
343 pub hit_test: Option<HitTestNode>,
344 pub has_hit_targets: bool,
345 pub has_origin_sinks: bool,
350 pub draws_within_bounds: bool,
355 pub isolation: IsolationReasons,
356 pub cache_policy: CachePolicy,
357 pub cache_hashes: LayerRasterCacheHashes,
358 pub cache_hashes_valid: bool,
359 pub children: Vec<RenderNode>,
360}
361
362impl Default for LayerNode {
363 fn default() -> Self {
364 Self {
365 node_id: None,
366 wraps: None,
367 local_bounds: Rect {
368 x: 0.0,
369 y: 0.0,
370 width: 0.0,
371 height: 0.0,
372 },
373 node_bounds: None,
374 transform_to_parent: ProjectiveTransform::identity(),
375 content_offset: Point::default(),
376 motion_context_animated: false,
377 translated_content_context: false,
378 translated_content_offset: Point::default(),
379 origin_in_parent: Point::default(),
380 graphics_layer: GraphicsLayer::default(),
381 clip_to_bounds: false,
382 shadow_clip: None,
383 hit_test: None,
384 has_hit_targets: false,
385 has_origin_sinks: false,
386 draws_within_bounds: false,
387 isolation: IsolationReasons::default(),
388 cache_policy: CachePolicy::None,
389 cache_hashes: LayerRasterCacheHashes::default(),
390 cache_hashes_valid: false,
391 children: Vec::new(),
392 }
393 }
394}
395
396pub const CONTAINED_DRAW_SLACK: f32 = 1.0;
399
400impl LayerNode {
401 pub fn content_draws_within_bounds(&self) -> bool {
405 if self.visual_clip_rect().is_some() {
406 return true;
407 }
408 let bounds = inflate_rect(self.local_bounds, CONTAINED_DRAW_SLACK);
409 self.children.iter().all(|child| match child {
410 RenderNode::DrawRun(run) => run
411 .recording
412 .bounds()
413 .is_none_or(|drawn| rect_within(drawn, bounds)),
414 RenderNode::Primitive(entry) => match &entry.node {
415 PrimitiveNode::Text(text) => text.draws_within(bounds),
416 PrimitiveNode::Draw(_) => false,
417 },
418 RenderNode::Layer(layer) => layer.draws_within_parent(bounds),
419 })
420 }
421
422 fn draws_within_parent(&self, bounds: Rect) -> bool {
426 self.draws_within_bounds
427 && self.graphics_layer.shadow_elevation <= 0.0
428 && self.effect().is_none()
429 && self.backdrop().is_none()
430 && rect_within(
431 quad_bounds(self.transform_to_parent.map_rect(self.local_bounds)),
432 bounds,
433 )
434 }
435
436 pub fn node_rect(&self) -> Rect {
438 self.node_bounds.unwrap_or(self.local_bounds)
439 }
440
441 pub fn clip_rect(&self) -> Option<Rect> {
442 (self.clip_to_bounds || self.graphics_layer.clip).then_some(self.local_bounds)
443 }
444
445 pub fn visual_clip_rect(&self) -> Option<Rect> {
450 self.clip_rect().or_else(|| {
451 (self.isolation.group_opacity || self.isolation.explicit_offscreen)
452 .then_some(self.local_bounds)
453 })
454 }
455
456 pub fn effect(&self) -> Option<&RenderEffect> {
457 self.graphics_layer.render_effect.as_ref()
458 }
459
460 pub fn backdrop(&self) -> Option<&RenderEffect> {
461 self.graphics_layer.backdrop_effect.as_ref()
462 }
463
464 pub fn opacity(&self) -> f32 {
465 self.graphics_layer.alpha
466 }
467
468 pub fn blend_mode(&self) -> BlendMode {
469 self.graphics_layer.blend_mode
470 }
471
472 pub fn color_filter(&self) -> Option<ColorFilter> {
473 self.graphics_layer.color_filter
474 }
475
476 pub fn target_content_hash(&self) -> u64 {
477 if self.cache_hashes_valid {
478 self.cache_hashes.target_content
479 } else {
480 crate::graph_hash::layer_raster_cache_hashes(self).target_content
481 }
482 }
483
484 pub fn motion_source_content_hash(&self) -> u64 {
485 crate::graph_hash::layer_motion_source_content_hash(self)
486 }
487
488 pub fn effect_hash(&self) -> u64 {
489 if self.cache_hashes_valid {
490 self.cache_hashes.effect
491 } else {
492 crate::graph_hash::layer_raster_cache_hashes(self).effect
493 }
494 }
495
496 pub fn recompute_raster_cache_hashes(&mut self) {
497 crate::graph_hash::recompute_layer_raster_cache_hashes(self);
498 }
499}
500
501#[derive(Clone)]
502pub enum RenderNode {
503 Primitive(PrimitiveEntry),
504 DrawRun(DrawRunNode),
511 Layer(Box<LayerNode>),
512}
513
514#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
523pub struct DrawCommandId {
524 pub node_id: NodeId,
525 pub command_index: u32,
526 pub placement: DrawPlacement,
527}
528
529#[derive(Clone, Debug, PartialEq)]
530pub struct DrawRunNode {
531 pub phase: PrimitivePhase,
532 pub command: Option<DrawCommandId>,
535 pub recording: Rc<CommandRecording>,
541 pub segments: Range<u32>,
544 pub summary: DrawRunSummary,
547}
548
549pub type DrawRunSummary = RecordingSummary;
551
552impl DrawRunNode {
553 pub fn new(phase: PrimitivePhase, primitives: Vec<DrawPrimitive>) -> Self {
554 Self::for_command(phase, None, primitives)
555 }
556
557 pub fn for_command(
558 phase: PrimitivePhase,
559 command: Option<DrawCommandId>,
560 primitives: Vec<DrawPrimitive>,
561 ) -> Self {
562 let recording = CommandRecording::from_primitives(primitives);
563 let segments = recording.all_segments();
564 Self::for_command_shared(phase, command, Rc::new(recording), segments)
565 }
566
567 pub fn for_command_shared(
568 phase: PrimitivePhase,
569 command: Option<DrawCommandId>,
570 recording: Rc<CommandRecording>,
571 segments: Range<u32>,
572 ) -> Self {
573 let summary = recording.summary_in(&segments);
574 Self {
575 phase,
576 command,
577 recording,
578 segments,
579 summary,
580 }
581 }
582
583 pub fn primitives(&self) -> impl Iterator<Item = DrawPrimitive> + '_ {
585 self.recording.primitives(self.segments.clone())
586 }
587
588 pub fn coverage_rects(&self) -> impl Iterator<Item = Rect> + '_ {
590 self.recording.coverage_rects(self.segments.clone())
591 }
592
593 pub fn len(&self) -> usize {
594 self.recording.len_in(&self.segments)
595 }
596
597 pub fn is_empty(&self) -> bool {
598 self.recording.is_empty_in(&self.segments)
599 }
600}
601
602#[derive(Clone)]
603pub struct RenderGraph {
604 pub root: LayerNode,
605}
606
607impl RenderGraph {
608 pub fn new(mut root: LayerNode) -> Self {
609 root.recompute_raster_cache_hashes();
610 Self { root }
611 }
612
613 pub fn node_count(&self) -> usize {
614 fn count_layer(layer: &LayerNode) -> usize {
615 1 + layer
616 .children
617 .iter()
618 .map(|child| match child {
619 RenderNode::Primitive(_) => 1,
620 RenderNode::DrawRun(run) => run.len(),
621 RenderNode::Layer(child_layer) => count_layer(child_layer),
622 })
623 .sum::<usize>()
624 }
625
626 count_layer(&self.root)
627 }
628
629 pub fn heap_bytes(&self) -> usize {
630 layer_heap_bytes(&self.root)
631 }
632
633 pub fn collect_retained_visual_observation_nodes(&self, nodes: &mut HashSet<NodeId>) {
635 fn collect(layer: &LayerNode, nodes: &mut HashSet<NodeId>) {
636 if let Some(node_id) = layer.node_id {
637 nodes.insert(node_id);
638 }
639 for child in &layer.children {
640 match child {
641 RenderNode::DrawRun(run) => {
642 if let Some(command) = run.command {
643 nodes.insert(command.node_id);
644 }
645 }
646 RenderNode::Layer(child) => collect(child, nodes),
647 RenderNode::Primitive(_) => {}
648 }
649 }
650 }
651
652 nodes.clear();
653 collect(&self.root, nodes);
654 }
655}
656
657fn layer_heap_bytes(layer: &LayerNode) -> usize {
658 size_of::<RenderNode>() * layer.children.capacity()
659 + layer
660 .children
661 .iter()
662 .map(render_node_heap_bytes)
663 .sum::<usize>()
664}
665
666fn render_node_heap_bytes(node: &RenderNode) -> usize {
667 match node {
668 RenderNode::Primitive(entry) => primitive_entry_heap_bytes(entry),
669 RenderNode::DrawRun(run) => {
670 run.recording.pod_heap_bytes()
671 + std::mem::size_of_val(run.recording.others())
672 + run
673 .recording
674 .others()
675 .iter()
676 .map(draw_primitive_heap_bytes)
677 .sum::<usize>()
678 }
679 RenderNode::Layer(layer) => size_of::<LayerNode>() + layer_heap_bytes(layer),
680 }
681}
682
683fn primitive_entry_heap_bytes(entry: &PrimitiveEntry) -> usize {
684 match &entry.node {
685 PrimitiveNode::Draw(draw) => {
686 size_of::<DrawPrimitiveNode>() + draw_primitive_heap_bytes(&draw.primitive)
687 }
688 PrimitiveNode::Text(text) => {
689 size_of::<TextPrimitiveNode>() + annotated_string_heap_bytes(&text.text)
690 }
691 }
692}
693
694fn draw_primitive_heap_bytes(primitive: &DrawPrimitive) -> usize {
695 match primitive {
696 DrawPrimitive::Content
697 | DrawPrimitive::Rect { .. }
698 | DrawPrimitive::RoundRect { .. }
699 | DrawPrimitive::Arc { .. } => 0,
700 DrawPrimitive::Blend { primitive, .. } => {
701 size_of::<DrawPrimitive>() + draw_primitive_heap_bytes(primitive)
702 }
703 DrawPrimitive::Image { .. } => 0,
704 DrawPrimitive::Text(text) => {
705 size_of::<cranpose_ui_graphics::TextPrimitive>()
706 + text.text.len()
707 + text
708 .style
709 .font_family
710 .as_ref()
711 .map_or(0, std::string::String::capacity)
712 }
713 DrawPrimitive::Shadow(shadow) => shadow_primitive_heap_bytes(shadow),
714 }
715}
716
717fn shadow_primitive_heap_bytes(shadow: &ShadowPrimitive) -> usize {
718 match shadow {
719 ShadowPrimitive::Drop { shape, .. } => {
720 size_of::<DrawPrimitive>() + draw_primitive_heap_bytes(shape)
721 }
722 ShadowPrimitive::Inner { fill, cutout, .. } => {
723 size_of::<DrawPrimitive>() * 2
724 + draw_primitive_heap_bytes(fill)
725 + draw_primitive_heap_bytes(cutout)
726 }
727 }
728}
729
730fn annotated_string_heap_bytes(text: &AnnotatedString) -> usize {
731 text.text.capacity()
732 + text.span_styles.capacity() * size_of::<usize>() * 2
733 + text.paragraph_styles.capacity() * size_of::<usize>() * 2
734 + text.string_annotations.capacity() * size_of::<usize>() * 2
735 + text.link_annotations.capacity() * size_of::<usize>() * 2
736 + text
737 .string_annotations
738 .iter()
739 .map(|annotation| {
740 annotation.item.tag.capacity() + annotation.item.annotation.capacity()
741 })
742 .sum::<usize>()
743 + text
744 .link_annotations
745 .iter()
746 .map(|annotation| match &annotation.item {
747 cranpose_ui::text::LinkAnnotation::Url(url) => url.capacity(),
748 cranpose_ui::text::LinkAnnotation::Clickable { tag, .. } => tag.capacity(),
749 })
750 .sum::<usize>()
751}
752
753pub fn quad_bounds(quad: [[f32; 2]; 4]) -> Rect {
754 let mut min_x = f32::INFINITY;
755 let mut min_y = f32::INFINITY;
756 let mut max_x = f32::NEG_INFINITY;
757 let mut max_y = f32::NEG_INFINITY;
758
759 for [x, y] in quad {
760 min_x = min_x.min(x);
761 min_y = min_y.min(y);
762 max_x = max_x.max(x);
763 max_y = max_y.max(y);
764 }
765
766 Rect {
767 x: min_x,
768 y: min_y,
769 width: (max_x - min_x).max(0.0),
770 height: (max_y - min_y).max(0.0),
771 }
772}
773
774fn multiply_matrices(lhs: [[f32; 3]; 3], rhs: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
775 let mut out = [[0.0; 3]; 3];
776 for row in 0..3 {
777 for col in 0..3 {
778 out[row][col] =
779 lhs[row][0] * rhs[0][col] + lhs[row][1] * rhs[1][col] + lhs[row][2] * rhs[2][col];
780 }
781 }
782 out
783}
784
785fn solve_homography(source: [[f32; 2]; 4], target: [[f32; 2]; 4]) -> Option<[f32; 8]> {
786 let mut matrix = [[0.0f32; 9]; 8];
787 for (index, (src, dst)) in source.into_iter().zip(target).enumerate() {
788 let row = index * 2;
789 let x = src[0];
790 let y = src[1];
791 let u = dst[0];
792 let v = dst[1];
793
794 matrix[row] = [x, y, 1.0, 0.0, 0.0, 0.0, -u * x, -u * y, u];
795 matrix[row + 1] = [0.0, 0.0, 0.0, x, y, 1.0, -v * x, -v * y, v];
796 }
797
798 for pivot in 0..8 {
799 let mut pivot_row = pivot;
800 let mut pivot_value = matrix[pivot][pivot].abs();
801 let mut candidate = pivot + 1;
802 while candidate < 8 {
803 let candidate_value = matrix[candidate][pivot].abs();
804 if candidate_value > pivot_value {
805 pivot_row = candidate;
806 pivot_value = candidate_value;
807 }
808 candidate += 1;
809 }
810
811 if pivot_value <= f32::EPSILON {
812 return None;
813 }
814
815 if pivot_row != pivot {
816 matrix.swap(pivot, pivot_row);
817 }
818
819 let divisor = matrix[pivot][pivot];
820 let mut col = pivot;
821 while col < 9 {
822 matrix[pivot][col] /= divisor;
823 col += 1;
824 }
825
826 for row in 0..8 {
827 if row == pivot {
828 continue;
829 }
830 let factor = matrix[row][pivot];
831 if factor.abs() <= f32::EPSILON {
832 continue;
833 }
834 let mut col = pivot;
835 while col < 9 {
836 matrix[row][col] -= factor * matrix[pivot][col];
837 col += 1;
838 }
839 }
840 }
841
842 let mut solution = [0.0f32; 8];
843 for index in 0..8 {
844 solution[index] = matrix[index][8];
845 }
846 Some(solution)
847}
848
849fn inflate_rect(rect: Rect, by: f32) -> Rect {
850 Rect {
851 x: rect.x - by,
852 y: rect.y - by,
853 width: rect.width + by * 2.0,
854 height: rect.height + by * 2.0,
855 }
856}
857
858fn rect_within(inner: Rect, outer: Rect) -> bool {
859 inner.x >= outer.x
860 && inner.y >= outer.y
861 && inner.x + inner.width <= outer.x + outer.width
862 && inner.y + inner.height <= outer.y + outer.height
863}
864
865#[cfg(test)]
866#[path = "tests/graph_tests.rs"]
867mod tests;