Make certain Bezier function parameters optional and other refactors (#713)
* Make certain parameters optional * Use builder pattern for project function's optional parameters * Address comments posted in bezier-math-lib discord channel * Minor changes to text * Address PR comments * Fix index.html * Nit * Replace builder pattern with simple struct * Move constants to a separate file Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
8f00a4071d
commit
6decc67571
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "vue-cli-service serve",
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
|
|
@ -1,17 +1,16 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="">
|
<html>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
<head>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<title>Bezier-rs Interactive Docs</title>
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
</head>
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
</head>
|
<body>
|
||||||
<body>
|
<noscript>
|
||||||
<noscript>
|
<strong>JavaScript is required</strong>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
</noscript>
|
||||||
</noscript>
|
<div id="app"></div>
|
||||||
<div id="app"></div>
|
</body>
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@
|
||||||
:cubicOptions="feature.cubicOptions"
|
:cubicOptions="feature.cubicOptions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<div id="svg-test" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -26,21 +24,6 @@ import { Point, WasmBezierInstance } from "@/utils/types";
|
||||||
import ExamplePane from "@/components/ExamplePane.vue";
|
import ExamplePane from "@/components/ExamplePane.vue";
|
||||||
import SliderExample from "@/components/SliderExample.vue";
|
import SliderExample from "@/components/SliderExample.vue";
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const testBezierLib = async () => {
|
|
||||||
import("@/../wasm/pkg").then((wasm) => {
|
|
||||||
const bezier = wasm.WasmBezier.new_quadratic([
|
|
||||||
[0, 0],
|
|
||||||
[50, 0],
|
|
||||||
[100, 100],
|
|
||||||
]);
|
|
||||||
const svgContainer = document.getElementById("svg-test");
|
|
||||||
if (svgContainer) {
|
|
||||||
svgContainer.innerHTML = bezier.to_svg();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const tSliderOptions = {
|
const tSliderOptions = {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
|
|
@ -49,6 +32,8 @@ const tSliderOptions = {
|
||||||
variable: "t",
|
variable: "t",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SCALE_UNIT_VECTOR_FACTOR = 50;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -63,7 +48,7 @@ export default defineComponent({
|
||||||
callback: (): void => {},
|
callback: (): void => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bezier through points",
|
name: "Bezier Through Points",
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
callback: (): void => {},
|
callback: (): void => {},
|
||||||
createThroughPoints: true,
|
createThroughPoints: true,
|
||||||
|
|
@ -137,26 +122,20 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Derivative",
|
name: "Tangent",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
const context = getContextFromCanvas(canvas);
|
const context = getContextFromCanvas(canvas);
|
||||||
|
|
||||||
const intersection = JSON.parse(bezier.compute(options.t));
|
const intersection = JSON.parse(bezier.compute(options.t));
|
||||||
const derivative = JSON.parse(bezier.derivative(options.t));
|
const tangent = JSON.parse(bezier.tangent(options.t));
|
||||||
const curveFactor = bezier.get_points().length - 1;
|
|
||||||
|
|
||||||
const tangentStart = {
|
|
||||||
x: intersection.x - derivative.x / curveFactor,
|
|
||||||
y: intersection.y - derivative.y / curveFactor,
|
|
||||||
};
|
|
||||||
const tangentEnd = {
|
const tangentEnd = {
|
||||||
x: intersection.x + derivative.x / curveFactor,
|
x: intersection.x + tangent.x * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
y: intersection.y + derivative.y / curveFactor,
|
y: intersection.y + tangent.y * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
drawLine(context, tangentStart, tangentEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
|
||||||
drawPoint(context, tangentStart, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
|
||||||
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
drawLine(context, intersection, tangentEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
drawPoint(context, tangentEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawPoint(context, tangentEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
},
|
},
|
||||||
template: markRaw(SliderExample),
|
template: markRaw(SliderExample),
|
||||||
|
|
@ -170,18 +149,13 @@ export default defineComponent({
|
||||||
const intersection = JSON.parse(bezier.compute(options.t));
|
const intersection = JSON.parse(bezier.compute(options.t));
|
||||||
const normal = JSON.parse(bezier.normal(options.t));
|
const normal = JSON.parse(bezier.normal(options.t));
|
||||||
|
|
||||||
const normalStart = {
|
|
||||||
x: intersection.x - normal.x * 20,
|
|
||||||
y: intersection.y - normal.y * 20,
|
|
||||||
};
|
|
||||||
const normalEnd = {
|
const normalEnd = {
|
||||||
x: intersection.x + normal.x * 20,
|
x: intersection.x - normal.x * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
y: intersection.y + normal.y * 20,
|
y: intersection.y - normal.y * SCALE_UNIT_VECTOR_FACTOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
drawLine(context, normalStart, normalEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
|
||||||
drawPoint(context, normalStart, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
|
||||||
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
|
drawLine(context, intersection, normalEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
drawPoint(context, normalEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
drawPoint(context, normalEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
},
|
},
|
||||||
template: markRaw(SliderExample),
|
template: markRaw(SliderExample),
|
||||||
|
|
@ -240,14 +214,16 @@ export default defineComponent({
|
||||||
name: "Local Extrema",
|
name: "Local Extrema",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||||
const context = getContextFromCanvas(canvas);
|
const context = getContextFromCanvas(canvas);
|
||||||
const dimensionColors = [COLORS.NON_INTERACTIVE.STROKE_1, COLORS.NON_INTERACTIVE.STROKE_2];
|
const dimensionColors = ["red", "green"];
|
||||||
const extrema: number[][] = JSON.parse(bezier.local_extrema());
|
const extrema: number[][] = JSON.parse(bezier.local_extrema());
|
||||||
extrema.forEach((tValues, index) => {
|
extrema.forEach((tValues, index) => {
|
||||||
tValues.forEach((t) => {
|
tValues.forEach((t) => {
|
||||||
const point = JSON.parse(bezier.compute(t));
|
const point: Point = JSON.parse(bezier.compute(t));
|
||||||
drawPoint(context, point, 4, dimensionColors[index]);
|
drawPoint(context, point, 4, dimensionColors[index]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
drawText(getContextFromCanvas(canvas), "X extrema", 5, canvas.height - 20, dimensionColors[0]);
|
||||||
|
drawText(getContextFromCanvas(canvas), "Y extrema", 5, canvas.height - 5, dimensionColors[1]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -255,7 +231,7 @@ export default defineComponent({
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
const context = getContextFromCanvas(canvas);
|
const context = getContextFromCanvas(canvas);
|
||||||
const rotatedBezier = bezier
|
const rotatedBezier = bezier
|
||||||
.rotate((options.angle * Math.PI) / 180)
|
.rotate(options.angle * Math.PI)
|
||||||
.get_points()
|
.get_points()
|
||||||
.map((p) => JSON.parse(p));
|
.map((p) => JSON.parse(p));
|
||||||
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
|
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
|
||||||
|
|
@ -265,25 +241,26 @@ export default defineComponent({
|
||||||
sliders: [
|
sliders: [
|
||||||
{
|
{
|
||||||
variable: "angle",
|
variable: "angle",
|
||||||
min: -90,
|
min: 0,
|
||||||
max: 90,
|
max: 2,
|
||||||
step: 5,
|
step: 1 / 16,
|
||||||
default: 15,
|
default: 1 / 8,
|
||||||
|
unit: "π",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Line Intersection",
|
name: "Intersect Line Segment",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||||
const context = getContextFromCanvas(canvas);
|
const context = getContextFromCanvas(canvas);
|
||||||
const line = [
|
const line = [
|
||||||
{ x: 150, y: 150 },
|
{ x: 150, y: 150 },
|
||||||
{ x: 30, y: 30 },
|
{ x: 20, y: 20 },
|
||||||
];
|
];
|
||||||
const mappedLine = line.map((p) => [p.x, p.y]);
|
const mappedLine = line.map((p) => [p.x, p.y]);
|
||||||
drawLine(context, line[0], line[1], COLORS.NON_INTERACTIVE.STROKE_1);
|
drawLine(context, line[0], line[1], COLORS.NON_INTERACTIVE.STROKE_1);
|
||||||
const intersections: Point[] = bezier.line_intersection(mappedLine).map((p) => JSON.parse(p));
|
const intersections: Point[] = bezier.intersect_line_segment(mappedLine).map((p) => JSON.parse(p));
|
||||||
intersections.forEach((p: Point) => {
|
intersections.forEach((p: Point) => {
|
||||||
drawPoint(context, p, 3, COLORS.NON_INTERACTIVE.STROKE_2);
|
drawPoint(context, p, 3, COLORS.NON_INTERACTIVE.STROKE_2);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<Example :title="title" :bezier="bezier" :callback="callback" :options="sliderData" :createThroughPoints="createThroughPoints" />
|
<Example :title="title" :bezier="bezier" :callback="callback" :options="sliderData" :createThroughPoints="createThroughPoints" />
|
||||||
<div v-for="(slider, index) in templateOptions.sliders" :key="index">
|
<div v-for="(slider, index) in templateOptions.sliders" :key="index">
|
||||||
<div class="slider_label">{{ slider.variable }} = {{ sliderData[slider.variable] }}</div>
|
<div class="slider_label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ sliderUnits[slider.variable] }}</div>
|
||||||
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
|
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -43,6 +43,7 @@ export default defineComponent({
|
||||||
const sliders = this.templateOptions.sliders;
|
const sliders = this.templateOptions.sliders;
|
||||||
return {
|
return {
|
||||||
sliderData: Object.assign({}, ...sliders.map((s) => ({ [s.variable]: s.default }))),
|
sliderData: Object.assign({}, ...sliders.map((s) => ({ [s.variable]: s.default }))),
|
||||||
|
sliderUnits: Object.assign({}, ...sliders.map((s) => ({ [s.variable]: s.unit }))),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export type SliderOption = {
|
||||||
step: number;
|
step: number;
|
||||||
default: number;
|
default: number;
|
||||||
variable: string;
|
variable: string;
|
||||||
|
unit?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TemplateOption = {
|
export type TemplateOption = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use bezier_rs::Bezier;
|
use bezier_rs::{Bezier, ProjectionOptions};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
@ -35,12 +35,12 @@ impl WasmBezier {
|
||||||
|
|
||||||
pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier {
|
pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier {
|
||||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||||
WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], t))
|
WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier {
|
pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier {
|
||||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||||
WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], t, midpoint_separation))
|
WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_start(&mut self, x: f64, y: f64) {
|
pub fn set_start(&mut self, x: f64, y: f64) {
|
||||||
|
|
@ -79,8 +79,8 @@ impl WasmBezier {
|
||||||
self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect()
|
self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derivative(&self, t: f64) -> JsValue {
|
pub fn tangent(&self, t: f64) -> JsValue {
|
||||||
vec_to_point(&self.0.derivative(t))
|
vec_to_point(&self.0.tangent(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normal(&self, t: f64) -> JsValue {
|
pub fn normal(&self, t: f64) -> JsValue {
|
||||||
|
|
@ -100,7 +100,7 @@ impl WasmBezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project(&self, x: f64, y: f64) -> JsValue {
|
pub fn project(&self, x: f64, y: f64) -> JsValue {
|
||||||
vec_to_point(&self.0.project(DVec2::new(x, y), 20, 1e-4, 3, 10))
|
vec_to_point(&self.0.project(DVec2::new(x, y), ProjectionOptions::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_extrema(&self) -> JsValue {
|
pub fn local_extrema(&self) -> JsValue {
|
||||||
|
|
@ -112,8 +112,8 @@ impl WasmBezier {
|
||||||
WasmBezier(self.0.rotate(angle))
|
WasmBezier(self.0.rotate(angle))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_intersection(&self, js_points: &JsValue) -> Vec<JsValue> {
|
pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec<JsValue> {
|
||||||
let line: [DVec2; 2] = js_points.into_serde().unwrap();
|
let line: [DVec2; 2] = js_points.into_serde().unwrap();
|
||||||
self.0.line_intersection(line).iter().map(|&p| vec_to_point(&p)).collect::<Vec<JsValue>>()
|
self.0.intersect_line_segment(line).iter().map(|&p| vec_to_point(&p)).collect::<Vec<JsValue>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/// Default `t` value used for the `curve_through_points` functions
|
||||||
|
pub const DEFAULT_T_VALUE: f64 = 0.5;
|
||||||
|
|
||||||
|
/// Default LUT step size in `compute_lookup_table` function
|
||||||
|
pub const DEFAULT_LUT_STEP_SIZE: i32 = 10;
|
||||||
|
|
||||||
|
/// Number of subdivisions used in `length` calculation
|
||||||
|
pub const LENGTH_SUBDIVISIONS: i32 = 1000;
|
||||||
|
|
||||||
|
/// Number of distances used in search algorithm for `project`
|
||||||
|
pub const NUM_DISTANCES: usize = 5;
|
||||||
|
|
||||||
|
/// Constants used to determine if `f64`'s are equivalent
|
||||||
|
pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
//! Bezier-rs: A Bezier Math Library for Rust
|
//! Bezier-rs: A Bezier Math Library for Rust
|
||||||
|
|
||||||
|
mod consts;
|
||||||
|
use consts::*;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use glam::{DMat2, DVec2};
|
use glam::{DMat2, DVec2};
|
||||||
|
|
||||||
mod utils;
|
/// Representation of the handle point(s) in a bezier curve.
|
||||||
|
|
||||||
/// Representation of the handle point(s) in a bezier segment.
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum BezierHandles {
|
enum BezierHandles {
|
||||||
/// Handles for a quadratic segment.
|
/// Handles for a quadratic curve.
|
||||||
Quadratic {
|
Quadratic {
|
||||||
/// Point representing the location of the single handle.
|
/// Point representing the location of the single handle.
|
||||||
handle: DVec2,
|
handle: DVec2,
|
||||||
},
|
},
|
||||||
/// Handles for a cubic segment.
|
/// Handles for a cubic curve.
|
||||||
Cubic {
|
Cubic {
|
||||||
/// Point representing the location of the handle associated to the start point.
|
/// Point representing the location of the handle associated to the start point.
|
||||||
handle_start: DVec2,
|
handle_start: DVec2,
|
||||||
|
|
@ -21,14 +23,38 @@ pub enum BezierHandles {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a bezier segment with 2D points.
|
/// Struct to represent optional parameters that can be passed to the `project` function.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ProjectionOptions {
|
||||||
|
/// Size of the lookup table for the initial passthrough. The default value is 20.
|
||||||
|
pub lut_size: i32,
|
||||||
|
/// Difference used between floating point numbers to be considered as equal. The default value is `0.0001`
|
||||||
|
pub convergence_epsilon: f64,
|
||||||
|
/// Controls the number of iterations needed to consider that minimum distance to have converged. The default value is 3.
|
||||||
|
pub convergence_limit: i32,
|
||||||
|
/// Controls the maximum total number of iterations to be used. The default value is 10.
|
||||||
|
pub iteration_limit: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProjectionOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
ProjectionOptions {
|
||||||
|
lut_size: 20,
|
||||||
|
convergence_epsilon: 1e-4,
|
||||||
|
convergence_limit: 3,
|
||||||
|
iteration_limit: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representation of a bezier curve with 2D points.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Bezier {
|
pub struct Bezier {
|
||||||
/// Start point of the bezier segment.
|
/// Start point of the bezier curve.
|
||||||
start: DVec2,
|
start: DVec2,
|
||||||
/// Start point of the bezier segment.
|
/// Start point of the bezier curve.
|
||||||
end: DVec2,
|
end: DVec2,
|
||||||
/// Handles of the bezier segment.
|
/// Handles of the bezier curve.
|
||||||
handles: BezierHandles,
|
handles: BezierHandles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,9 +101,11 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a quadratic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
|
/// Create a quadratic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
|
||||||
|
/// - `t` - A representation of how far along the curve the provided point should occur at. The default value is 0.5.
|
||||||
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
|
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
|
||||||
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
|
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
|
||||||
pub fn quadratic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: f64) -> Self {
|
pub fn quadratic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: Option<f64>) -> Self {
|
||||||
|
let t = t.unwrap_or(DEFAULT_T_VALUE);
|
||||||
if t == 0. {
|
if t == 0. {
|
||||||
return Bezier::from_quadratic_dvec2(point_on_curve, point_on_curve, end);
|
return Bezier::from_quadratic_dvec2(point_on_curve, point_on_curve, end);
|
||||||
}
|
}
|
||||||
|
|
@ -89,17 +117,20 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a cubic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
|
/// Create a cubic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
|
||||||
|
/// - `t` - A representation of how far along the curve the provided point should occur at. The default value is 0.5.
|
||||||
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
|
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
|
||||||
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
|
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
|
||||||
/// - `midpoint_separation` is a representation of the how wide the resulting curve will be around `t` on the curve. This parameter designates the distance between the `e1` and `e2` defined in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer.
|
/// - `midpoint_separation` - A representation of how wide the resulting curve will be around `t` on the curve. This parameter designates the distance between the `e1` and `e2` defined in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer. It is an optional parameter and the default value is the distance between the points `B` and `C` defined in the primer.
|
||||||
pub fn cubic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: f64, midpoint_separation: f64) -> Self {
|
pub fn cubic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: Option<f64>, midpoint_separation: Option<f64>) -> Self {
|
||||||
|
let t = t.unwrap_or(DEFAULT_T_VALUE);
|
||||||
if t == 0. {
|
if t == 0. {
|
||||||
return Bezier::from_cubic_dvec2(point_on_curve, point_on_curve, end, end);
|
return Bezier::from_cubic_dvec2(point_on_curve, point_on_curve, end, end);
|
||||||
}
|
}
|
||||||
if t == 1. {
|
if t == 1. {
|
||||||
return Bezier::from_cubic_dvec2(start, start, point_on_curve, point_on_curve);
|
return Bezier::from_cubic_dvec2(start, start, point_on_curve, point_on_curve);
|
||||||
}
|
}
|
||||||
let [a, b, _] = utils::compute_abc_for_cubic_through_points(start, point_on_curve, end, t);
|
let [a, b, c] = utils::compute_abc_for_cubic_through_points(start, point_on_curve, end, t);
|
||||||
|
let midpoint_separation = midpoint_separation.unwrap_or_else(|| b.distance(c));
|
||||||
let distance_between_start_and_end = (end - start) / (start.distance(end));
|
let distance_between_start_and_end = (end - start) / (start.distance(end));
|
||||||
let e1 = b - (distance_between_start_and_end * midpoint_separation);
|
let e1 = b - (distance_between_start_and_end * midpoint_separation);
|
||||||
let e2 = b + (distance_between_start_and_end * midpoint_separation * (1. - t) / t);
|
let e2 = b + (distance_between_start_and_end * midpoint_separation * (1. - t) / t);
|
||||||
|
|
@ -113,8 +144,8 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert to SVG.
|
/// Convert to SVG.
|
||||||
// TODO: Allow modifying the viewport, width and height
|
|
||||||
pub fn to_svg(&self) -> String {
|
pub fn to_svg(&self) -> String {
|
||||||
|
// TODO: Allow modifying the viewport, width and height
|
||||||
let m_path = format!("M {} {}", self.start.x, self.start.y);
|
let m_path = format!("M {} {}", self.start.x, self.start.y);
|
||||||
let handles_path = match self.handles {
|
let handles_path = match self.handles {
|
||||||
BezierHandles::Quadratic { handle } => {
|
BezierHandles::Quadratic { handle } => {
|
||||||
|
|
@ -228,7 +259,7 @@ impl Bezier {
|
||||||
/// Return a selection of equidistant points on the bezier curve.
|
/// Return a selection of equidistant points on the bezier curve.
|
||||||
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||||
pub fn compute_lookup_table(&self, steps: Option<i32>) -> Vec<DVec2> {
|
pub fn compute_lookup_table(&self, steps: Option<i32>) -> Vec<DVec2> {
|
||||||
let steps_unwrapped = steps.unwrap_or(10);
|
let steps_unwrapped = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
||||||
let ratio: f64 = 1.0 / (steps_unwrapped as f64);
|
let ratio: f64 = 1.0 / (steps_unwrapped as f64);
|
||||||
let mut steps_array = Vec::with_capacity((steps_unwrapped + 1) as usize);
|
let mut steps_array = Vec::with_capacity((steps_unwrapped + 1) as usize);
|
||||||
|
|
||||||
|
|
@ -240,14 +271,13 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an approximation of the length of the bezier curve.
|
/// Return an approximation of the length of the bezier curve.
|
||||||
/// Code example from <https://gamedev.stackexchange.com/questions/5373/moving-ships-between-two-planets-along-a-bezier-missing-some-equations-for-acce/5427#5427>.
|
|
||||||
pub fn length(&self) -> f64 {
|
pub fn length(&self) -> f64 {
|
||||||
|
// Code example from <https://gamedev.stackexchange.com/questions/5373/moving-ships-between-two-planets-along-a-bezier-missing-some-equations-for-acce/5427#5427>.
|
||||||
|
|
||||||
// We will use an approximate approach where
|
// We will use an approximate approach where
|
||||||
// we split the curve into many subdivisions
|
// we split the curve into many subdivisions
|
||||||
// and calculate the euclidean distance between the two endpoints of the subdivision
|
// and calculate the euclidean distance between the two endpoints of the subdivision
|
||||||
const SUBDIVISIONS: i32 = 1000;
|
let lookup_table = self.compute_lookup_table(Some(LENGTH_SUBDIVISIONS));
|
||||||
|
|
||||||
let lookup_table = self.compute_lookup_table(Some(SUBDIVISIONS));
|
|
||||||
let mut approx_curve_length = 0.0;
|
let mut approx_curve_length = 0.0;
|
||||||
let mut prev_point = lookup_table[0];
|
let mut prev_point = lookup_table[0];
|
||||||
// calculate approximate distance between subdivision
|
// calculate approximate distance between subdivision
|
||||||
|
|
@ -340,12 +370,15 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the closest point on the curve to the provided point.
|
/// Returns the closest point on the curve to the provided point.
|
||||||
/// Uses a searching algorithm akin to binary search that can be customized using the following parameters:
|
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
|
||||||
/// - `lut_size` - Size of the lookup table for the initial passthrough.
|
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> DVec2 {
|
||||||
/// - `convergence_epsilon` - Difference used between floating point numbers to be considered as equal.
|
let ProjectionOptions {
|
||||||
/// - `convergence_limit` - Controls the number of iterations needed to consider that minimum distance to have converged.
|
lut_size,
|
||||||
/// - `iteration_limit` - Controls the maximum total number of iterations to be used.
|
convergence_epsilon,
|
||||||
pub fn project(&self, point: DVec2, lut_size: i32, convergence_epsilon: f64, convergence_limit: i32, iteration_limit: i32) -> DVec2 {
|
convergence_limit,
|
||||||
|
iteration_limit,
|
||||||
|
} = options;
|
||||||
|
|
||||||
// First find the closest point from the results of a lookup table
|
// First find the closest point from the results of a lookup table
|
||||||
let lut = self.compute_lookup_table(Some(lut_size));
|
let lut = self.compute_lookup_table(Some(lut_size));
|
||||||
let (minimum_position, minimum_distance) = utils::get_closest_point_in_lut(&lut, point);
|
let (minimum_position, minimum_distance) = utils::get_closest_point_in_lut(&lut, point);
|
||||||
|
|
@ -367,8 +400,8 @@ impl Bezier {
|
||||||
let mut iteration_count = 0;
|
let mut iteration_count = 0;
|
||||||
// Counter to identify how many iterations have had a similar result. Used for convergence test
|
// Counter to identify how many iterations have had a similar result. Used for convergence test
|
||||||
let mut convergence_count = 0;
|
let mut convergence_count = 0;
|
||||||
|
|
||||||
// Store calculated distances to minimize unnecessary recomputations
|
// Store calculated distances to minimize unnecessary recomputations
|
||||||
const NUM_DISTANCES: usize = 5;
|
|
||||||
let mut distances: [f64; NUM_DISTANCES] = [
|
let mut distances: [f64; NUM_DISTANCES] = [
|
||||||
point.distance(lut[0.max(minimum_position - 1) as usize]),
|
point.distance(lut[0.max(minimum_position - 1) as usize]),
|
||||||
0.,
|
0.,
|
||||||
|
|
@ -491,7 +524,7 @@ impl Bezier {
|
||||||
|
|
||||||
/// Returns a list of points where the provided line segment intersects with the Bezier curve.
|
/// Returns a list of points where the provided line segment intersects with the Bezier curve.
|
||||||
/// - `line` - A line segment expected to be received in the format of `[start_point, end_point]`.
|
/// - `line` - A line segment expected to be received in the format of `[start_point, end_point]`.
|
||||||
pub fn line_intersection(&self, line: [DVec2; 2]) -> Vec<DVec2> {
|
pub fn intersect_line_segment(&self, line: [DVec2; 2]) -> Vec<DVec2> {
|
||||||
// Rotate the bezier and the line by the angle that the line makes with the x axis
|
// Rotate the bezier and the line by the angle that the line makes with the x axis
|
||||||
let slope = line[1] - line[0];
|
let slope = line[1] - line[0];
|
||||||
let angle = slope.angle_between(DVec2::new(1., 0.));
|
let angle = slope.angle_between(DVec2::new(1., 0.));
|
||||||
|
|
@ -527,25 +560,26 @@ impl Bezier {
|
||||||
};
|
};
|
||||||
let min = line[0].min(line[1]);
|
let min = line[0].min(line[1]);
|
||||||
let max = line[0].max(line[1]);
|
let max = line[0].max(line[1]);
|
||||||
let max_abs_diff = 1e-4;
|
|
||||||
|
|
||||||
list_intersection_t
|
list_intersection_t
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&&t| utils::f64_approximately_in_range(t, 0., 1., max_abs_diff))
|
.filter(|&&t| utils::f64_approximately_in_range(t, 0., 1., MAX_ABSOLUTE_DIFFERENCE))
|
||||||
.map(|&t| self.unrestricted_compute(t))
|
.map(|&t| self.unrestricted_compute(t))
|
||||||
.filter(|&point| utils::dvec2_approximately_in_range(point, min, max, max_abs_diff).all())
|
.filter(|&point| utils::dvec2_approximately_in_range(point, min, max, MAX_ABSOLUTE_DIFFERENCE).all())
|
||||||
.collect::<Vec<DVec2>>()
|
.collect::<Vec<DVec2>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::Bezier;
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
fn compare_points(p1: DVec2, p2: DVec2) -> bool {
|
fn compare_points(p1: DVec2, p2: DVec2) -> bool {
|
||||||
utils::dvec2_compare(p1, p2, 1e-3).all()
|
utils::dvec2_compare(p1, p2, MAX_ABSOLUTE_DIFFERENCE).all()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -554,13 +588,13 @@ mod tests {
|
||||||
let p2 = DVec2::new(140., 30.);
|
let p2 = DVec2::new(140., 30.);
|
||||||
let p3 = DVec2::new(160., 170.);
|
let p3 = DVec2::new(160., 170.);
|
||||||
|
|
||||||
let bezier1 = Bezier::quadratic_through_points(p1, p2, p3, 0.5);
|
let bezier1 = Bezier::quadratic_through_points(p1, p2, p3, None);
|
||||||
assert!(compare_points(bezier1.compute(0.5), p2));
|
assert!(compare_points(bezier1.compute(0.5), p2));
|
||||||
|
|
||||||
let bezier2 = Bezier::quadratic_through_points(p1, p2, p3, 0.8);
|
let bezier2 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.8));
|
||||||
assert!(compare_points(bezier2.compute(0.8), p2));
|
assert!(compare_points(bezier2.compute(0.8), p2));
|
||||||
|
|
||||||
let bezier3 = Bezier::quadratic_through_points(p1, p2, p3, 0.);
|
let bezier3 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.));
|
||||||
assert!(compare_points(bezier3.compute(0.), p2));
|
assert!(compare_points(bezier3.compute(0.), p2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -570,28 +604,30 @@ mod tests {
|
||||||
let p2 = DVec2::new(60., 140.);
|
let p2 = DVec2::new(60., 140.);
|
||||||
let p3 = DVec2::new(160., 160.);
|
let p3 = DVec2::new(160., 160.);
|
||||||
|
|
||||||
let bezier1 = Bezier::cubic_through_points(p1, p2, p3, 0.3, 10.);
|
let bezier1 = Bezier::cubic_through_points(p1, p2, p3, Some(0.3), Some(10.));
|
||||||
assert!(compare_points(bezier1.compute(0.3), p2));
|
assert!(compare_points(bezier1.compute(0.3), p2));
|
||||||
|
|
||||||
let bezier2 = Bezier::cubic_through_points(p1, p2, p3, 0.8, 91.7);
|
let bezier2 = Bezier::cubic_through_points(p1, p2, p3, Some(0.8), Some(91.7));
|
||||||
assert!(compare_points(bezier2.compute(0.8), p2));
|
assert!(compare_points(bezier2.compute(0.8), p2));
|
||||||
|
|
||||||
let bezier3 = Bezier::cubic_through_points(p1, p2, p3, 0., 91.7);
|
let bezier3 = Bezier::cubic_through_points(p1, p2, p3, Some(0.), Some(91.7));
|
||||||
assert!(compare_points(bezier3.compute(0.), p2));
|
assert!(compare_points(bezier3.compute(0.), p2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project() {
|
fn project() {
|
||||||
|
let project_options = ProjectionOptions::default();
|
||||||
|
|
||||||
let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.);
|
let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.);
|
||||||
assert!(bezier1.project(DVec2::new(100., 100.), 20, 0.0001, 3, 10) == DVec2::new(56., 90.));
|
assert!(bezier1.project(DVec2::new(100., 100.), project_options) == DVec2::new(56., 90.));
|
||||||
assert!(bezier1.project(DVec2::new(0., 0.), 20, 0.0001, 3, 10) == DVec2::new(4., 4.));
|
assert!(bezier1.project(DVec2::new(0., 0.), project_options) == DVec2::new(4., 4.));
|
||||||
|
|
||||||
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
||||||
assert!(bezier2.project(DVec2::new(100., 0.), 20, 0.0001, 3, 10) == DVec2::new(0., 0.));
|
assert!(bezier2.project(DVec2::new(100., 0.), project_options) == DVec2::new(0., 0.));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_intersection_quadratic() {
|
fn intersect_line_segment_quadratic() {
|
||||||
let p1 = DVec2::new(30., 50.);
|
let p1 = DVec2::new(30., 50.);
|
||||||
let p2 = DVec2::new(140., 30.);
|
let p2 = DVec2::new(140., 30.);
|
||||||
let p3 = DVec2::new(160., 170.);
|
let p3 = DVec2::new(160., 170.);
|
||||||
|
|
@ -599,18 +635,18 @@ mod tests {
|
||||||
// Intersection at edge of curve
|
// Intersection at edge of curve
|
||||||
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||||
let line1 = [DVec2::new(20., 50.), DVec2::new(40., 50.)];
|
let line1 = [DVec2::new(20., 50.), DVec2::new(40., 50.)];
|
||||||
let intersections1 = bezier1.line_intersection(line1);
|
let intersections1 = bezier1.intersect_line_segment(line1);
|
||||||
assert!(intersections1.len() == 1);
|
assert!(intersections1.len() == 1);
|
||||||
assert!(compare_points(intersections1[0], p1));
|
assert!(compare_points(intersections1[0], p1));
|
||||||
|
|
||||||
// Intersection in the middle of curve
|
// Intersection in the middle of curve
|
||||||
let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)];
|
let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)];
|
||||||
let intersections2 = bezier1.line_intersection(line2);
|
let intersections2 = bezier1.intersect_line_segment(line2);
|
||||||
assert!(compare_points(intersections2[0], DVec2::new(47.77355, 47.77354)));
|
assert!(compare_points(intersections2[0], DVec2::new(47.77355, 47.77354)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_intersection_cubic() {
|
fn intersect_line_segment_cubic() {
|
||||||
let p1 = DVec2::new(30., 30.);
|
let p1 = DVec2::new(30., 30.);
|
||||||
let p2 = DVec2::new(60., 140.);
|
let p2 = DVec2::new(60., 140.);
|
||||||
let p3 = DVec2::new(150., 30.);
|
let p3 = DVec2::new(150., 30.);
|
||||||
|
|
@ -619,13 +655,13 @@ mod tests {
|
||||||
let bezier = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
let bezier = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
||||||
// Intersection at edge of curve, Discriminant > 0
|
// Intersection at edge of curve, Discriminant > 0
|
||||||
let line1 = [DVec2::new(20., 30.), DVec2::new(40., 30.)];
|
let line1 = [DVec2::new(20., 30.), DVec2::new(40., 30.)];
|
||||||
let intersections1 = bezier.line_intersection(line1);
|
let intersections1 = bezier.intersect_line_segment(line1);
|
||||||
assert!(intersections1.len() == 1);
|
assert!(intersections1.len() == 1);
|
||||||
assert!(compare_points(intersections1[0], p1));
|
assert!(compare_points(intersections1[0], p1));
|
||||||
|
|
||||||
// Intersection at edge and in middle of curve, Discriminant < 0
|
// Intersection at edge and in middle of curve, Discriminant < 0
|
||||||
let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)];
|
let line2 = [DVec2::new(150., 150.), DVec2::new(30., 30.)];
|
||||||
let intersections2 = bezier.line_intersection(line2);
|
let intersections2 = bezier.intersect_line_segment(line2);
|
||||||
assert!(intersections2.len() == 2);
|
assert!(intersections2.len() == 2);
|
||||||
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)));
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,10 @@ pub fn solve_reformatted_cubic(discriminant: f64, a: f64, p: f64, q: f64) -> Vec
|
||||||
pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
|
pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
|
||||||
if a.abs() <= 1e-5 {
|
if a.abs() <= 1e-5 {
|
||||||
if b.abs() <= 1e-5 {
|
if b.abs() <= 1e-5 {
|
||||||
// if both a and b are approximately 0, treat as a linear problem
|
// If both a and b are approximately 0, treat as a linear problem
|
||||||
solve_linear(c, d)
|
solve_linear(c, d)
|
||||||
} else {
|
} else {
|
||||||
// if a is approximately 0, treat as a quadratic problem
|
// If a is approximately 0, treat as a quadratic problem
|
||||||
let discriminant = c * c - 4. * b * d;
|
let discriminant = c * c - 4. * b * d;
|
||||||
solve_quadratic(discriminant, 2. * b, c, d)
|
solve_quadratic(discriminant, 2. * b, c, d)
|
||||||
}
|
}
|
||||||
|
|
@ -159,6 +159,7 @@ pub fn dvec2_approximately_in_range(point: DVec2, min: DVec2, max: DVec2, max_ab
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_solve_cubic() {
|
fn test_solve_cubic() {
|
||||||
|
|
@ -180,14 +181,14 @@ mod tests {
|
||||||
// discriminant > 0
|
// discriminant > 0
|
||||||
let roots4 = solve_cubic(1., 3., 0., 2.);
|
let roots4 = solve_cubic(1., 3., 0., 2.);
|
||||||
assert!(roots4.len() == 1);
|
assert!(roots4.len() == 1);
|
||||||
assert!(f64_compare(roots4[0], -3.196, 1e-3));
|
assert!(f64_compare(roots4[0], -3.196, MAX_ABSOLUTE_DIFFERENCE));
|
||||||
|
|
||||||
// discriminant < 0
|
// discriminant < 0
|
||||||
let roots5 = solve_cubic(1., 3., 0., -1.);
|
let roots5 = solve_cubic(1., 3., 0., -1.);
|
||||||
assert!(roots5.len() == 3);
|
assert!(roots5.len() == 3);
|
||||||
assert!(f64_compare(roots5[0], 0.532, 1e-3));
|
assert!(f64_compare(roots5[0], 0.532, MAX_ABSOLUTE_DIFFERENCE));
|
||||||
assert!(f64_compare(roots5[1], -2.879, 1e-3));
|
assert!(f64_compare(roots5[1], -2.879, MAX_ABSOLUTE_DIFFERENCE));
|
||||||
assert!(f64_compare(roots5[2], -0.653, 1e-3));
|
assert!(f64_compare(roots5[2], -0.653, MAX_ABSOLUTE_DIFFERENCE));
|
||||||
|
|
||||||
// quadratic
|
// quadratic
|
||||||
let roots6 = solve_cubic(0., 3., 0., -3.);
|
let roots6 = solve_cubic(0., 3., 0., -3.);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue