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