mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 00:48:12 +08:00
Implement Bezier scale and offset (#718)
* Implement scale and offset * Add tests for offset and reduce * Added scale to documentation page * address comments Co-authored-by: Hannah Li <hannahli2010@gmail.com> Co-authored-by: Thomas Cheng <Androxium@users.noreply.github.com> Co-authored-by: Rob Nadal <RobNadal@users.noreply.github.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Thomas Cheng
Rob Nadal
parent
19483a9a35
commit
30e5d3c8ec
@@ -25,7 +25,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, markRaw } from "vue";
|
import { defineComponent, markRaw } from "vue";
|
||||||
|
|
||||||
import { drawText, drawPoint, drawBezier, drawLine, getContextFromCanvas, drawBezierHelper, COLORS } from "@/utils/drawing";
|
import { drawBezier, drawBezierHelper, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
|
||||||
import { BezierCurveType, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
import { BezierCurveType, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
||||||
|
|
||||||
import ExamplePane from "@/components/ExamplePane.vue";
|
import ExamplePane from "@/components/ExamplePane.vue";
|
||||||
@@ -194,8 +194,8 @@ export default defineComponent({
|
|||||||
const normal = JSON.parse(bezier.normal(options.t));
|
const normal = JSON.parse(bezier.normal(options.t));
|
||||||
|
|
||||||
const normalEnd = {
|
const normalEnd = {
|
||||||
x: intersection.x - normal.x * SCALE_UNIT_VECTOR_FACTOR,
|
x: intersection.x + normal.x * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
y: intersection.y - normal.y * SCALE_UNIT_VECTOR_FACTOR,
|
y: intersection.y + normal.y * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
@@ -283,6 +283,31 @@ export default defineComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Bounding Box",
|
||||||
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||||
|
const context = getContextFromCanvas(canvas);
|
||||||
|
const bboxPoints: Point[] = JSON.parse(bezier.bounding_box());
|
||||||
|
const minPoint = bboxPoints[0];
|
||||||
|
const maxPoint = bboxPoints[1];
|
||||||
|
drawLine(context, minPoint, { x: minPoint.x, y: maxPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
drawLine(context, minPoint, { x: maxPoint.x, y: minPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
drawLine(context, maxPoint, { x: minPoint.x, y: maxPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
drawLine(context, maxPoint, { x: maxPoint.x, y: minPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Inflections",
|
||||||
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||||
|
const context = getContextFromCanvas(canvas);
|
||||||
|
const inflections: number[] = JSON.parse(bezier.inflections());
|
||||||
|
inflections.forEach((t) => {
|
||||||
|
const point = JSON.parse(bezier.evaluate(t));
|
||||||
|
drawPoint(context, point, 4, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
curveDegrees: new Set([BezierCurveType.Cubic]),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "De Casteljau Points",
|
name: "De Casteljau Points",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
@@ -357,29 +382,32 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bounding Box",
|
name: "Offset",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
const context = getContextFromCanvas(canvas);
|
const context = getContextFromCanvas(canvas);
|
||||||
const bboxPoints: Point[] = JSON.parse(bezier.bounding_box());
|
const curves: Point[][] = JSON.parse(bezier.offset(options.distance));
|
||||||
const minPoint = bboxPoints[0];
|
curves.forEach((points, index) => {
|
||||||
const maxPoint = bboxPoints[1];
|
if (points.length === 2) {
|
||||||
drawLine(context, minPoint, { x: minPoint.x, y: maxPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawLine(context, points[0], points[1], `hsl(${40 * index}, 100%, 50%)`);
|
||||||
drawLine(context, minPoint, { x: maxPoint.x, y: minPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
} else {
|
||||||
drawLine(context, maxPoint, { x: minPoint.x, y: maxPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawCurve(context, points, `hsl(${40 * index}, 100%, 50%)`);
|
||||||
drawLine(context, maxPoint, { x: maxPoint.x, y: minPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Inflections",
|
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
|
||||||
const context = getContextFromCanvas(canvas);
|
|
||||||
const inflections: number[] = JSON.parse(bezier.inflections());
|
|
||||||
inflections.forEach((t) => {
|
|
||||||
const point = JSON.parse(bezier.evaluate(t));
|
|
||||||
drawPoint(context, point, 4, COLORS.NON_INTERACTIVE.STROKE_1);
|
|
||||||
});
|
});
|
||||||
|
drawPoint(context, curves[0][0], 4, "hsl(0, 100%, 50%)");
|
||||||
|
drawPoint(context, curves[curves.length - 1][curves[0].length - 1], 4, `hsl(${40 * (curves.length - 1)}, 100%, 50%)`);
|
||||||
|
},
|
||||||
|
template: markRaw(SliderExample),
|
||||||
|
templateOptions: {
|
||||||
|
sliders: [
|
||||||
|
{
|
||||||
|
variable: "distance",
|
||||||
|
min: -50,
|
||||||
|
max: 50,
|
||||||
|
step: 1,
|
||||||
|
default: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
curveDegrees: new Set([BezierCurveType.Cubic]),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
subpathFeatures: [
|
subpathFeatures: [
|
||||||
|
|||||||
@@ -128,6 +128,16 @@ impl WasmBezier {
|
|||||||
to_js_value(local_extrema)
|
to_js_value(local_extrema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bounding_box(&self) -> JsValue {
|
||||||
|
let bbox_points: [Point; 2] = self.0.bounding_box().map(|p| Point { x: p.x, y: p.y });
|
||||||
|
to_js_value(bbox_points)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inflections(&self) -> JsValue {
|
||||||
|
let inflections = self.0.inflections();
|
||||||
|
to_js_value(inflections)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
|
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
|
||||||
let hull = self
|
let hull = self
|
||||||
.0
|
.0
|
||||||
@@ -152,13 +162,8 @@ impl WasmBezier {
|
|||||||
to_js_value(bezier_points)
|
to_js_value(bezier_points)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bounding_box(&self) -> JsValue {
|
pub fn offset(&self, distance: f64) -> JsValue {
|
||||||
let bbox_points: [Point; 2] = self.0.bounding_box().map(|p| Point { x: p.x, y: p.y });
|
let bezier_points: Vec<Vec<Point>> = self.0.offset(distance).into_iter().map(bezier_to_points).collect();
|
||||||
to_js_value(bbox_points)
|
to_js_value(bezier_points)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inflections(&self) -> JsValue {
|
|
||||||
let inflections = self.0.inflections();
|
|
||||||
to_js_value(inflections)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-3
@@ -10,7 +10,7 @@ pub use subpath::*;
|
|||||||
use glam::{DMat2, DVec2};
|
use glam::{DMat2, DVec2};
|
||||||
|
|
||||||
/// Representation of the handle point(s) in a bezier segment.
|
/// Representation of the handle point(s) in a bezier segment.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
enum BezierHandles {
|
enum BezierHandles {
|
||||||
Linear,
|
Linear,
|
||||||
/// Handles for a quadratic curve.
|
/// Handles for a quadratic curve.
|
||||||
@@ -688,6 +688,9 @@ impl Bezier {
|
|||||||
/// 2. The on-curve point for `t = 0.5` must occur roughly in the center of the polygon defined by the curve's endpoint normals.
|
/// 2. The on-curve point for `t = 0.5` must occur roughly in the center of the polygon defined by the curve's endpoint normals.
|
||||||
/// See [the offset section](https://pomax.github.io/bezierinfo/#offsetting) of Pomax's bezier curve primer for more details.
|
/// See [the offset section](https://pomax.github.io/bezierinfo/#offsetting) of Pomax's bezier curve primer for more details.
|
||||||
fn is_scalable(&self) -> bool {
|
fn is_scalable(&self) -> bool {
|
||||||
|
if self.handles == BezierHandles::Linear {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Verify all the handles are located on a single side of the curve.
|
// Verify all the handles are located on a single side of the curve.
|
||||||
if let BezierHandles::Cubic { handle_start, handle_end } = self.handles {
|
if let BezierHandles::Cubic { handle_start, handle_end } = self.handles {
|
||||||
let angle_1 = (self.end - self.start).angle_between(handle_start - self.start);
|
let angle_1 = (self.end - self.start).angle_between(handle_start - self.start);
|
||||||
@@ -758,6 +761,8 @@ impl Bezier {
|
|||||||
if f64::abs(t1 - t2) >= step_size {
|
if f64::abs(t1 - t2) >= step_size {
|
||||||
segment = subcurve.trim(t1, t2);
|
segment = subcurve.trim(t1, t2);
|
||||||
result.push(segment);
|
result.push(segment);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
t1 = t2;
|
t1 = t2;
|
||||||
}
|
}
|
||||||
@@ -832,18 +837,72 @@ impl Bezier {
|
|||||||
pub fn inflections(&self) -> Vec<f64> {
|
pub fn inflections(&self) -> Vec<f64> {
|
||||||
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
|
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scale will translate a bezier curve a fixed distance away from its original position, and stretch/compress the transformed curve to match the translation ratio.
|
||||||
|
/// Note that not all bezier curves are possible to scale, so this function asserts that the provided curve is scalable.
|
||||||
|
/// A proof for why this is true can be found in the [Curve offsetting section](https://pomax.github.io/bezierinfo/#offsetting) of Pomax's bezier curve primer.
|
||||||
|
/// `scale` takes the parameter `distance`, which is the distance away from the curve that the new one will be scaled to. Positive values will scale the curve in the
|
||||||
|
/// same direction as the endpoint normals, while negative values will scale in the opposite direction.
|
||||||
|
fn scale(&self, distance: f64) -> Bezier {
|
||||||
|
assert!(self.is_scalable(), "The curve provided to scale is not scalable. Reduce the curve first.");
|
||||||
|
|
||||||
|
let normal_start = self.normal(0.);
|
||||||
|
let normal_end = self.normal(1.);
|
||||||
|
|
||||||
|
// If normal unit vectors are equal, then the lines are parallel
|
||||||
|
if normal_start.abs_diff_eq(normal_end, MAX_ABSOLUTE_DIFFERENCE) {
|
||||||
|
return self.translate(distance * normal_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the intersection point of the endpoint normals
|
||||||
|
let intersection = utils::line_intersection(self.start, normal_start, self.end, normal_end);
|
||||||
|
|
||||||
|
let should_flip_direction = (self.start - intersection).normalize().abs_diff_eq(normal_start, MAX_ABSOLUTE_DIFFERENCE);
|
||||||
|
self.apply_transformation(&|point| {
|
||||||
|
let mut direction_unit_vector = (intersection - point).normalize();
|
||||||
|
if should_flip_direction {
|
||||||
|
direction_unit_vector *= -1.;
|
||||||
|
}
|
||||||
|
point + distance * direction_unit_vector
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Offset will get all the reduceable subcurves, and for each subcurve, it will scale the subcurve a set distance away from the original curve.
|
||||||
|
/// Note that not all bezier curves are possible to offset, so this function first reduces the curve to scalable segments and then offsets those segments.
|
||||||
|
/// A proof for why this is true can be found in the [Curve offsetting section](https://pomax.github.io/bezierinfo/#offsetting) of Pomax's bezier curve primer.
|
||||||
|
/// Offset takes the following parameter:
|
||||||
|
/// - `distance` - The distance away from the curve that the new one will be offset to. Positive values will offset the curve in the same direction as the endpoint normals,
|
||||||
|
/// while negative values will offset in the opposite direction.
|
||||||
|
pub fn offset(&self, distance: f64) -> Vec<Bezier> {
|
||||||
|
let mut reduced = self.reduce(None);
|
||||||
|
reduced.iter_mut().for_each(|bezier| *bezier = bezier.scale(distance));
|
||||||
|
reduced
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||||
use crate::utils;
|
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
|
// Compare points by allowing some maximum absolute difference to account for floating point errors
|
||||||
fn compare_points(p1: DVec2, p2: DVec2) -> bool {
|
fn compare_points(p1: DVec2, p2: DVec2) -> bool {
|
||||||
utils::dvec2_compare(p1, p2, MAX_ABSOLUTE_DIFFERENCE).all()
|
p1.abs_diff_eq(p2, MAX_ABSOLUTE_DIFFERENCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare vectors of points by allowing some maximum absolute difference to account for floating point errors
|
||||||
|
fn compare_vector_of_points(a: Vec<DVec2>, b: Vec<DVec2>) -> bool {
|
||||||
|
a.len() == b.len() && a.into_iter().zip(b.into_iter()).all(|(p1, p2)| p1.abs_diff_eq(p2, MAX_ABSOLUTE_DIFFERENCE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare vectors of beziers by allowing some maximum absolute difference between points to account for floating point errors
|
||||||
|
fn compare_vector_of_beziers(beziers: Vec<Bezier>, expected_bezier_points: Vec<Vec<DVec2>>) -> bool {
|
||||||
|
beziers
|
||||||
|
.iter()
|
||||||
|
.zip(expected_bezier_points.iter())
|
||||||
|
.all(|(&a, b)| compare_vector_of_points(a.get_points().collect::<Vec<DVec2>>(), b.to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -947,4 +1006,45 @@ mod tests {
|
|||||||
assert!(compare_points(intersections2[0], p1));
|
assert!(compare_points(intersections2[0], p1));
|
||||||
assert!(compare_points(intersections2[1], DVec2::new(85.84, 85.84)));
|
assert!(compare_points(intersections2[1], DVec2::new(85.84, 85.84)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn offset() {
|
||||||
|
let p1 = DVec2::new(30., 50.);
|
||||||
|
let p2 = DVec2::new(140., 30.);
|
||||||
|
let p3 = DVec2::new(160., 170.);
|
||||||
|
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||||
|
let expected_bezier_points1 = vec![
|
||||||
|
vec![DVec2::new(31.7888, 59.8387), DVec2::new(44.5924, 57.46446), DVec2::new(56.09375, 57.5)],
|
||||||
|
vec![DVec2::new(56.09375, 57.5), DVec2::new(94.94197, 56.5019), DVec2::new(117.6473, 84.5936)],
|
||||||
|
vec![DVec2::new(117.6473, 84.5936), DVec2::new(142.3985, 113.403), DVec2::new(150.1005, 171.4142)],
|
||||||
|
];
|
||||||
|
assert!(compare_vector_of_beziers(bezier1.offset(10.), expected_bezier_points1));
|
||||||
|
|
||||||
|
let p4 = DVec2::new(32., 77.);
|
||||||
|
let p5 = DVec2::new(169., 25.);
|
||||||
|
let p6 = DVec2::new(164., 157.);
|
||||||
|
let bezier2 = Bezier::from_quadratic_dvec2(p4, p5, p6);
|
||||||
|
let expected_bezier_points2 = vec![
|
||||||
|
vec![DVec2::new(42.6458, 105.04758), DVec2::new(75.0218, 91.9939), DVec2::new(98.09357, 92.3043)],
|
||||||
|
vec![DVec2::new(98.09357, 92.3043), DVec2::new(116.5995, 88.5479), DVec2::new(123.9055, 102.0401)],
|
||||||
|
vec![DVec2::new(123.9055, 102.0401), DVec2::new(136.6087, 116.9522), DVec2::new(134.1761, 147.9324)],
|
||||||
|
vec![DVec2::new(134.1761, 147.9324), DVec2::new(134.1812, 151.7987), DVec2::new(134.0215, 155.86445)],
|
||||||
|
];
|
||||||
|
assert!(compare_vector_of_beziers(bezier2.offset(30.), expected_bezier_points2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reduce() {
|
||||||
|
let p1 = DVec2::new(0., 0.);
|
||||||
|
let p2 = DVec2::new(50., 50.);
|
||||||
|
let p3 = DVec2::new(0., 0.);
|
||||||
|
let bezier = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||||
|
|
||||||
|
let expected_bezier_points = vec![
|
||||||
|
vec![DVec2::new(0., 0.), DVec2::new(0.5, 0.5), DVec2::new(0.989, 0.989)],
|
||||||
|
vec![DVec2::new(0.989, 0.989), DVec2::new(2.705, 2.705), DVec2::new(4.2975, 4.2975)],
|
||||||
|
vec![DVec2::new(4.2975, 4.2975), DVec2::new(5.6625, 5.6625), DVec2::new(6.9375, 6.9375)],
|
||||||
|
];
|
||||||
|
assert!(compare_vector_of_beziers(bezier.reduce(None), expected_bezier_points));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,33 @@ pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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());
|
||||||
|
|
||||||
|
// Find the intersection when the first line is vertical
|
||||||
|
if f64_compare(point1_slope_vector.x, 0., MAX_ABSOLUTE_DIFFERENCE) {
|
||||||
|
let m2 = point2_slope_vector.y / point2_slope_vector.x;
|
||||||
|
let b2 = point2.y - m2 * point2.x;
|
||||||
|
DVec2::new(point1.x, point1.x * m2 + b2)
|
||||||
|
}
|
||||||
|
// Find the intersection when the second line is vertical
|
||||||
|
else if f64_compare(point2_slope_vector.x, 0., MAX_ABSOLUTE_DIFFERENCE) {
|
||||||
|
let m1 = point1_slope_vector.y / point1_slope_vector.x;
|
||||||
|
let b1 = point1.y - m1 * point1.x;
|
||||||
|
DVec2::new(point2.x, point2.x * m1 + b1)
|
||||||
|
}
|
||||||
|
// Find the intersection where neither line is vertical
|
||||||
|
else {
|
||||||
|
let m1 = point1_slope_vector.y / point1_slope_vector.x;
|
||||||
|
let b1 = point1.y - m1 * point1.x;
|
||||||
|
let m2 = point2_slope_vector.y / point2_slope_vector.x;
|
||||||
|
let b2 = point2.y - m2 * point2.x;
|
||||||
|
let intersection_x = (b2 - b1) / (m1 - m2);
|
||||||
|
DVec2::new(intersection_x, intersection_x * m1 + b1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compare two `f64` numbers with a provided max absolute value difference.
|
/// Compare two `f64` numbers with a provided max absolute value difference.
|
||||||
pub fn f64_compare(f1: f64, f2: f64, max_abs_diff: f64) -> bool {
|
pub fn f64_compare(f1: f64, f2: f64, max_abs_diff: f64) -> bool {
|
||||||
(f1 - f2).abs() < max_abs_diff
|
(f1 - f2).abs() < max_abs_diff
|
||||||
@@ -215,4 +242,27 @@ mod tests {
|
|||||||
let roots7 = solve_cubic(0., 0., 1., -1.);
|
let roots7 = solve_cubic(0., 0., 1., -1.);
|
||||||
assert!(roots7 == vec![1.]);
|
assert!(roots7 == vec![1.]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_intersection() {
|
||||||
|
// y = 2x + 10
|
||||||
|
// y = 5x + 4
|
||||||
|
// intersect at (2, 14)
|
||||||
|
|
||||||
|
let start1 = DVec2::new(0., 10.);
|
||||||
|
let end1 = DVec2::new(0., 4.);
|
||||||
|
let start_direction1 = DVec2::new(1., 2.);
|
||||||
|
let end_direction1 = DVec2::new(1., 5.);
|
||||||
|
assert!(line_intersection(start1, start_direction1, end1, end_direction1) == DVec2::new(2., 14.));
|
||||||
|
|
||||||
|
// y = x
|
||||||
|
// y = -x + 8
|
||||||
|
// intersect at (4, 4)
|
||||||
|
|
||||||
|
let start2 = DVec2::new(0., 0.);
|
||||||
|
let end2 = DVec2::new(8., 0.);
|
||||||
|
let start_direction2 = DVec2::new(1., 1.);
|
||||||
|
let end_direction2 = DVec2::new(1., -1.);
|
||||||
|
assert!(line_intersection(start2, start_direction2, end2, end_direction2) == DVec2::new(4., 4.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user