diff --git a/bezier-rs/docs/interactive-docs/src/App.vue b/bezier-rs/docs/interactive-docs/src/App.vue index 7c398a3b..58748211 100644 --- a/bezier-rs/docs/interactive-docs/src/App.vue +++ b/bezier-rs/docs/interactive-docs/src/App.vue @@ -269,7 +269,8 @@ export default defineComponent({ callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record, mouseLocation?: Point): void => { if (mouseLocation != null) { const context = getContextFromCanvas(canvas); - const closestPoint = JSON.parse(bezier.project(mouseLocation.x, mouseLocation.y)); + const t = bezier.project(mouseLocation.x, mouseLocation.y); + const closestPoint = JSON.parse(bezier.evaluate(t)); drawLine(context, mouseLocation, closestPoint, COLORS.NON_INTERACTIVE.STROKE_1); } }, @@ -376,7 +377,7 @@ export default defineComponent({ }, }, { - name: "Intersect Line Segment", + name: "Intersect (Line Segment)", callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => { const context = getContextFromCanvas(canvas); const line = [ @@ -385,12 +386,44 @@ export default defineComponent({ ]; const mappedLine = line.map((p) => [p.x, p.y]); drawLine(context, line[0], line[1], COLORS.NON_INTERACTIVE.STROKE_1); - const intersections: Point[] = bezier.intersect_line_segment(mappedLine).map((p) => JSON.parse(p)); - intersections.forEach((p: Point) => { + const intersections: Float64Array = bezier.intersect_line_segment(mappedLine); + intersections.forEach((t: number) => { + const p = JSON.parse(bezier.evaluate(t)); drawPoint(context, p, 3, COLORS.NON_INTERACTIVE.STROKE_2); }); }, }, + { + name: "Intersect (Cubic Segment)", + callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record): void => { + const context = getContextFromCanvas(canvas); + const points = [ + { x: 40, y: 20 }, + { x: 100, y: 40 }, + { x: 40, y: 120 }, + { x: 175, y: 140 }, + ]; + const mappedPoints = points.map((p) => [p.x, p.y]); + drawCurve(context, points, COLORS.NON_INTERACTIVE.STROKE_1, 1); + const intersections: Float64Array = bezier.intersect_cubic_segment(mappedPoints, options.error); + intersections.forEach((t: number) => { + const p = JSON.parse(bezier.evaluate(t)); + drawPoint(context, p, 3, COLORS.NON_INTERACTIVE.STROKE_2); + }); + }, + template: markRaw(SliderExample), + templateOptions: { + sliders: [ + { + variable: "error", + min: 0.1, + max: 2, + step: 0.1, + default: 0.5, + }, + ], + }, + }, { name: "Reduce", callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => { diff --git a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts index 9b4e537f..838a2df4 100644 --- a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts +++ b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts @@ -59,9 +59,9 @@ export const drawText = (ctx: CanvasRenderingContext2D, text: string, x: number, ctx.fillText(text, x, y); }; -export const drawCurve = (ctx: CanvasRenderingContext2D, points: Point[], strokeColor = COLORS.INTERACTIVE.STROKE_1): void => { +export const drawCurve = (ctx: CanvasRenderingContext2D, points: Point[], strokeColor = COLORS.INTERACTIVE.STROKE_1, lineWidth = 2): void => { ctx.strokeStyle = strokeColor; - ctx.lineWidth = 2; + ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); diff --git a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs index 65a19bd8..5b1a7805 100644 --- a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs +++ b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs @@ -119,8 +119,8 @@ impl WasmBezier { WasmBezier(self.0.trim(t1, t2)) } - pub fn project(&self, x: f64, y: f64) -> JsValue { - vec_to_point(&self.0.project(DVec2::new(x, y), ProjectionOptions::default())) + pub fn project(&self, x: f64, y: f64) -> f64 { + self.0.project(DVec2::new(x, y), ProjectionOptions::default()) } pub fn local_extrema(&self) -> JsValue { @@ -152,9 +152,26 @@ impl WasmBezier { WasmBezier(self.0.rotate(angle)) } - pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec { - let line: [DVec2; 2] = js_points.into_serde().unwrap(); - self.0.intersect_line_segment(line).iter().map(|&p| vec_to_point(&p)).collect::>() + fn intersect(&self, curve: &Bezier, error: Option) -> Vec { + self.0.intersections(curve, error) + } + + pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec { + let points: [DVec2; 2] = js_points.into_serde().unwrap(); + let line = Bezier::from_linear_dvec2(points[0], points[1]); + self.intersect(&line, None) + } + + pub fn intersect_quadratic_segment(&self, js_points: &JsValue, error: f64) -> Vec { + let points: [DVec2; 3] = js_points.into_serde().unwrap(); + let quadratic = Bezier::from_quadratic_dvec2(points[0], points[1], points[2]); + self.intersect(&quadratic, Some(error)) + } + + pub fn intersect_cubic_segment(&self, js_points: &JsValue, error: f64) -> Vec { + let points: [DVec2; 4] = js_points.into_serde().unwrap(); + let cubic = Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3]); + self.intersect(&cubic, Some(error)) } pub fn reduce(&self) -> JsValue { diff --git a/bezier-rs/lib/src/lib.rs b/bezier-rs/lib/src/lib.rs index 028d97bd..1cf936d4 100644 --- a/bezier-rs/lib/src/lib.rs +++ b/bezier-rs/lib/src/lib.rs @@ -466,9 +466,9 @@ impl Bezier { bezier_starting_at_t1.split(adjusted_t2)[t2_split_side] } - /// Returns the closest point on the curve to the provided point. + /// Returns the `t` value that corresponds to the closest point on the curve to the provided point. /// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure. - pub fn project(&self, point: DVec2, options: ProjectionOptions) -> DVec2 { + pub fn project(&self, point: DVec2, options: ProjectionOptions) -> f64 { let ProjectionOptions { lut_size, convergence_epsilon, @@ -554,7 +554,7 @@ impl Bezier { } } - self.evaluate(final_t) + final_t } /// Returns two lists of `t`-values representing the local extrema of the `x` and `y` parametric curves respectively. @@ -622,61 +622,115 @@ impl Bezier { self.apply_transformation(&|point| point + translation) } + /// Implementation of the algorithm to find curve intersections by iterating on bounding boxes. + /// - `self_original_t_interval` - Used to identify the `t` values of the original parent of `self` that the current iteration is representing. + /// Note that the `t` interval the other curve is not needed since we want to return `t` with respect to it. + fn intersections_between_subcurves(&self, self_original_t_interval: [f64; 2], other: &Bezier, error: f64) -> Vec { + let bounding_box1 = self.bounding_box(); + let bounding_box2 = other.bounding_box(); + + // Get the `t` interval of the original parent of `self` and determine the middle `t` value + let [curve1_start_t, curve1_end_t] = self_original_t_interval; + let curve1_mid_t = curve1_start_t + (curve1_end_t - curve1_start_t) / 2.; + + let error_threshold = DVec2::new(error, error); + + // Check if the bounding boxes overlap + if utils::do_rectangles_overlap(bounding_box1, bounding_box2) { + // If bounding boxes are within the error threshold (i.e. are small enough), we have found an intersection + if (bounding_box1[1] - bounding_box1[0]).lt(&error_threshold) && (bounding_box2[1] - bounding_box2[0]).lt(&error_threshold) { + // Use the middle t value + return vec![curve1_mid_t]; + } + + // Split curves in half and repeat with the combinations of the two halves of each curve + let [split_1_a, split_1_b] = self.split(0.5); + let [split_2_a, split_2_b] = other.split(0.5); + + // Get the new `t` intervals for the split halves of `self` + let interval_1_a = [curve1_start_t, curve1_mid_t]; + let interval_1_b = [curve1_mid_t, curve1_end_t]; + [ + split_1_a.intersections_between_subcurves(interval_1_a, &split_2_a, error), + split_1_a.intersections_between_subcurves(interval_1_a, &split_2_b, error), + split_1_b.intersections_between_subcurves(interval_1_b, &split_2_a, error), + split_1_b.intersections_between_subcurves(interval_1_b, &split_2_b, error), + ] + .concat() + } else { + vec![] + } + } + // TODO: Use an `impl Iterator` return type instead of a `Vec` - // TODO: Change this to `intersect(&self, other: &Bezier)` to also work on quadratic and cubic segments - // TODO: (or keep this and add two more functions that perform the logic, and make the `intersect` function call the correct one) - /// Returns a list of points where the provided line segment intersects with the Bezier curve. If the provided segment is colinear with the bezier, zero intersection points will be returned. - /// - `line` - A line segment expected to be received in the format of `[start_point, end_point]`. - pub fn intersect_line_segment(&self, line: [DVec2; 2]) -> Vec { - // Rotate the bezier and the line by the angle that the line makes with the x axis - let slope = line[1] - line[0]; - let angle = slope.angle_between(DVec2::new(1., 0.)); - let rotation_matrix = DMat2::from_angle(angle); - let rotated_bezier = self.apply_transformation(&|point| rotation_matrix.mul_vec2(point)); - let rotated_line = [rotation_matrix.mul_vec2(line[0]), rotation_matrix.mul_vec2(line[1])]; + /// Returns a list of `t` values that correspond to intersection points between the current bezier curve and the provided one. The returned `t` values are with respect to the current bezier, not the provided parameter. + /// If either curve is linear, then zero intersection points will be returned along colinear segments. + /// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point. + pub fn intersections(&self, curve: &Bezier, error: Option) -> Vec { + let error = error.unwrap_or(0.5); + if curve.handles == BezierHandles::Linear { + // Rotate the bezier and the line by the angle that the line makes with the x axis + let slope = curve.end - curve.start; + let angle = slope.angle_between(DVec2::new(1., 0.)); + let rotation_matrix = DMat2::from_angle(angle); + let rotated_bezier = self.apply_transformation(&|point| rotation_matrix.mul_vec2(point)); + let rotated_line = [rotation_matrix.mul_vec2(curve.start), rotation_matrix.mul_vec2(curve.end)]; - // Translate the bezier such that the line becomes aligned on top of the x-axis - let vertical_distance = rotated_line[0].y; - let translated_bezier = rotated_bezier.translate(DVec2::new(0., -vertical_distance)); + // Translate the bezier such that the line becomes aligned on top of the x-axis + let vertical_distance = rotated_line[0].y; + let translated_bezier = rotated_bezier.translate(DVec2::new(0., -vertical_distance)); - // Compute the roots of the resulting bezier curve - let list_intersection_t = match translated_bezier.handles { - BezierHandles::Linear => { - // If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots - let a = translated_bezier.end.y - translated_bezier.start.y; - let b = translated_bezier.start.y; - utils::solve_linear(a, b) - } - BezierHandles::Quadratic { handle } => { - let a = translated_bezier.start.y - 2. * handle.y + translated_bezier.end.y; - let b = 2. * (handle.y - translated_bezier.start.y); - let c = translated_bezier.start.y; + // Compute the roots of the resulting bezier curve + let list_intersection_t = match translated_bezier.handles { + BezierHandles::Linear => { + // If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots + let a = translated_bezier.end.y - translated_bezier.start.y; + let b = translated_bezier.start.y; + utils::solve_linear(a, b) + } + BezierHandles::Quadratic { handle } => { + let a = translated_bezier.start.y - 2. * handle.y + translated_bezier.end.y; + let b = 2. * (handle.y - translated_bezier.start.y); + let c = translated_bezier.start.y; - let discriminant = b * b - 4. * a * c; - let two_times_a = 2. * a; + let discriminant = b * b - 4. * a * c; + let two_times_a = 2. * a; - utils::solve_quadratic(discriminant, two_times_a, b, c) - } - BezierHandles::Cubic { handle_start, handle_end } => { - let start_y = translated_bezier.start.y; - let a = -start_y + 3. * handle_start.y - 3. * handle_end.y + translated_bezier.end.y; - let b = 3. * start_y - 6. * handle_start.y + 3. * handle_end.y; - let c = -3. * start_y + 3. * handle_start.y; - let d = start_y; + utils::solve_quadratic(discriminant, two_times_a, b, c) + } + BezierHandles::Cubic { handle_start, handle_end } => { + let start_y = translated_bezier.start.y; + let a = -start_y + 3. * handle_start.y - 3. * handle_end.y + translated_bezier.end.y; + let b = 3. * start_y - 6. * handle_start.y + 3. * handle_end.y; + let c = -3. * start_y + 3. * handle_start.y; + let d = start_y; - utils::solve_cubic(a, b, c, d) - } - }; + utils::solve_cubic(a, b, c, d) + } + }; - let min = line[0].min(line[1]); - let max = line[0].max(line[1]); + let min = curve.start.min(curve.end); + let max = curve.start.max(curve.end); - list_intersection_t - .iter() - .filter(|&&t| utils::f64_approximately_in_range(t, 0., 1., MAX_ABSOLUTE_DIFFERENCE)) - .map(|&t| self.unrestricted_evaluate(t)) - .filter(|&point| utils::dvec2_approximately_in_range(point, min, max, MAX_ABSOLUTE_DIFFERENCE).all()) - .collect::>() + return list_intersection_t + .into_iter() + // Accept the t value if it is approximately in [0, 1] and if the coresponding coordinates are within the range of the linear line + .filter(|&t| { + utils::f64_approximately_in_range(t, 0., 1., MAX_ABSOLUTE_DIFFERENCE) + && utils::dvec2_approximately_in_range(self.unrestricted_evaluate(t), min, max, MAX_ABSOLUTE_DIFFERENCE).all() + }) + // Ensure the returned value is within the correct range + .map(|t| t.clamp(0., 1.)) + .collect::>(); + } + + // If the self is linear, then use the implementation for intersections with linear lines + if self.handles == BezierHandles::Linear { + return curve.intersections(self, Some(error)); + } + + // Otherwise, use bounding box to determine intersections + self.intersections_between_subcurves([0., 1.], curve, error) } /// Returns a list of lists of points representing the De Casteljau points for all iterations at the point corresponding to `t` using De Casteljau's algorithm. @@ -925,8 +979,13 @@ mod tests { .all(|(&a, b)| compare_vector_of_points(a.get_points().collect::>(), b.to_vec())) } + // Compare vectors of points with some maximum allowed absolute difference between the values + fn compare_vec_of_points(vec1: Vec, vec2: Vec, max_absolute_difference: f64) -> bool { + vec1.into_iter().zip(vec2).all(|(p1, p2)| p1.abs_diff_eq(p2, max_absolute_difference)) + } + #[test] - fn quadratic_from_points() { + fn test_quadratic_from_points() { let p1 = DVec2::new(30., 50.); let p2 = DVec2::new(140., 30.); let p3 = DVec2::new(160., 170.); @@ -942,7 +1001,7 @@ mod tests { } #[test] - fn cubic_through_points() { + fn test_cubic_through_points() { let p1 = DVec2::new(30., 30.); let p2 = DVec2::new(60., 140.); let p3 = DVec2::new(160., 160.); @@ -958,55 +1017,55 @@ mod tests { } #[test] - fn project() { + fn test_project() { let project_options = ProjectionOptions::default(); let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.); - assert!(bezier1.project(DVec2::new(100., 100.), project_options) == DVec2::new(56., 90.)); - assert!(bezier1.project(DVec2::new(0., 0.), project_options) == DVec2::new(4., 4.)); + assert!(bezier1.evaluate(bezier1.project(DVec2::new(100., 100.), project_options)) == DVec2::new(56., 90.)); + assert!(bezier1.evaluate(bezier1.project(DVec2::new(0., 0.), project_options)) == DVec2::new(4., 4.)); let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.); - assert!(bezier2.project(DVec2::new(100., 0.), project_options) == DVec2::new(0., 0.)); + assert!(bezier2.evaluate(bezier2.project(DVec2::new(100., 0.), project_options)) == DVec2::new(0., 0.)); } #[test] - fn intersect_line_segment_linear() { + fn test_intersect_line_segment_linear() { let p1 = DVec2::new(30., 60.); let p2 = DVec2::new(140., 120.); // Intersection at edge of curve - let bezier1 = Bezier::from_linear_dvec2(p1, p2); - let line1 = [DVec2::new(20., 60.), DVec2::new(70., 60.)]; - let intersections1 = bezier1.intersect_line_segment(line1); + let bezier = Bezier::from_linear_dvec2(p1, p2); + let line1 = Bezier::from_linear_coordinates(20., 60., 70., 60.); + let intersections1 = bezier.intersections(&line1, None); assert!(intersections1.len() == 1); - assert!(compare_points(intersections1[0], DVec2::new(30., 60.))); + assert!(compare_points(bezier.evaluate(intersections1[0]), DVec2::new(30., 60.))); // Intersection in the middle of curve - let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)]; - let intersections2 = bezier1.intersect_line_segment(line2); - assert!(compare_points(intersections2[0], DVec2::new(96., 96.))); + let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.); + let intersections2 = bezier.intersections(&line2, None); + assert!(compare_points(bezier.evaluate(intersections2[0]), DVec2::new(96., 96.))); } #[test] - fn intersect_line_segment_quadratic() { + fn test_intersect_line_segment_quadratic() { let p1 = DVec2::new(30., 50.); let p2 = DVec2::new(140., 30.); let p3 = DVec2::new(160., 170.); // Intersection at edge of curve - let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3); - let line1 = [DVec2::new(20., 50.), DVec2::new(40., 50.)]; - let intersections1 = bezier1.intersect_line_segment(line1); + let bezier = Bezier::from_quadratic_dvec2(p1, p2, p3); + let line1 = Bezier::from_linear_coordinates(20., 50., 40., 50.); + let intersections1 = bezier.intersections(&line1, None); assert!(intersections1.len() == 1); - assert!(compare_points(intersections1[0], p1)); + assert!(compare_points(bezier.evaluate(intersections1[0]), p1)); // Intersection in the middle of curve - let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)]; - let intersections2 = bezier1.intersect_line_segment(line2); - assert!(compare_points(intersections2[0], DVec2::new(47.77355, 47.77354))); + let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.); + let intersections2 = bezier.intersections(&line2, None); + assert!(compare_points(bezier.evaluate(intersections2[0]), DVec2::new(47.77355, 47.77354))); } #[test] - fn intersect_line_segment_cubic() { + fn test_intersect_line_segment_cubic() { let p1 = DVec2::new(30., 30.); let p2 = DVec2::new(60., 140.); let p3 = DVec2::new(150., 30.); @@ -1014,21 +1073,35 @@ mod tests { let bezier = Bezier::from_cubic_dvec2(p1, p2, p3, p4); // Intersection at edge of curve, Discriminant > 0 - let line1 = [DVec2::new(20., 30.), DVec2::new(40., 30.)]; - let intersections1 = bezier.intersect_line_segment(line1); + let line1 = Bezier::from_linear_coordinates(20., 30., 40., 30.); + let intersections1 = bezier.intersections(&line1, None); assert!(intersections1.len() == 1); - assert!(compare_points(intersections1[0], p1)); + assert!(compare_points(bezier.evaluate(intersections1[0]), p1)); // Intersection at edge and in middle of curve, Discriminant < 0 - let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)]; - let intersections2 = bezier.intersect_line_segment(line2); + let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.); + let intersections2 = bezier.intersections(&line2, None); assert!(intersections2.len() == 2); - assert!(compare_points(intersections2[0], p1)); - assert!(compare_points(intersections2[1], DVec2::new(85.84, 85.84))); + assert!(compare_points(bezier.evaluate(intersections2[0]), p1)); + assert!(compare_points(bezier.evaluate(intersections2[1]), DVec2::new(85.84, 85.84))); } #[test] - fn offset() { + fn test_intersect_curve() { + let bezier1 = Bezier::from_cubic_coordinates(30., 30., 60., 140., 150., 30., 160., 160.); + let bezier2 = Bezier::from_quadratic_coordinates(175., 140., 20., 20., 120., 20.); + + let intersections = bezier1.intersections(&bezier2, None); + let intersections2 = bezier2.intersections(&bezier1, None); + assert!(compare_vec_of_points( + intersections.iter().map(|&t| bezier1.evaluate(t)).collect(), + intersections2.iter().map(|&t| bezier2.evaluate(t)).collect(), + 2. + )); + } + + #[test] + fn test_offset() { let p1 = DVec2::new(30., 50.); let p2 = DVec2::new(140., 30.); let p3 = DVec2::new(160., 170.); @@ -1054,7 +1127,7 @@ mod tests { } #[test] - fn reduce() { + fn test_reduce() { let p1 = DVec2::new(0., 0.); let p2 = DVec2::new(50., 50.); let p3 = DVec2::new(0., 0.); diff --git a/bezier-rs/lib/src/utils.rs b/bezier-rs/lib/src/utils.rs index 39ac14b3..7d122502 100644 --- a/bezier-rs/lib/src/utils.rs +++ b/bezier-rs/lib/src/utils.rs @@ -146,6 +146,14 @@ pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> Vec { } } +/// Determine if two rectangles have any overlap. The rectangles are represented by a pair of coordinates that designate the top left and bottom right corners (in a graphical coordinate system). +pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> bool { + let [bottom_left1, top_right1] = rectangle1; + let [bottom_left2, top_right2] = rectangle2; + + top_right1.x >= bottom_left2.x && top_right2.x >= bottom_left1.x && top_right2.y >= bottom_left1.y && top_right1.y >= bottom_left2.y +} + /// Returns the intersection of two lines. The lines are given by a point on the line and its slope (represented by a vector). pub fn line_intersection(point1: DVec2, point1_slope_vector: DVec2, point2: DVec2, point2_slope_vector: DVec2) -> DVec2 { assert!(point1_slope_vector.normalize() != point2_slope_vector.normalize()); @@ -243,6 +251,20 @@ mod tests { assert!(roots7 == vec![1.]); } + #[test] + fn test_do_rectangles_overlap() { + // Rectangles overlap + assert!(do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(20., 20.)], [DVec2::new(10., 10.), DVec2::new(30., 20.)])); + // Rectangles share a side + assert!(do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(10., 10.), DVec2::new(30., 30.)])); + // Rectangle inside the other + assert!(do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(2., 2.), DVec2::new(6., 4.)])); + // No overlap, rectangles are beside each other + assert!(!do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(20., 0.), DVec2::new(30., 10.)])); + // No overlap, rectangles are above and below each other + assert!(!do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(0., 20.), DVec2::new(20., 30.)])); + } + #[test] fn test_find_intersection() { // y = 2x + 10