Bezier-rs: Update interactive demo site UI/UX (#1085)
* wip - change demo iframe styling * fix typo * change tVariant select from radio buttons to dropdown * Added iframe styling * fix linting errors * Integrated tvariant picker as input options * Updated points in demos * Lint * Clean up CSS --------- Co-authored-by: Thomas Cheng <contact.chengthomas@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
bfbabbc4dc
commit
834cb1a227
|
|
@ -250,16 +250,16 @@ impl Bezier {
|
||||||
/// The returned `t` values are with respect to the current bezier, not the provided parameter.
|
/// The returned `t` values are with respect to the current bezier, not the provided parameter.
|
||||||
/// If the provided curve is linear, then zero intersection points will be returned along colinear segments.
|
/// If the provided curve is linear, then zero intersection points will be returned along colinear segments.
|
||||||
/// - `error` - For intersections where the provided bezier is non-linear, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
/// - `error` - For intersections where the provided bezier is non-linear, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||||
/// - `minimum_seperation` - The minimum difference between adjacent `t` values in sorted order
|
/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order
|
||||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#bezier/intersect-cubic/solo" title="Intersections Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#bezier/intersect-cubic/solo" title="Intersections Demo"></iframe>
|
||||||
pub fn intersections(&self, other: &Bezier, error: Option<f64>, minimum_seperation: Option<f64>) -> Vec<f64> {
|
pub fn intersections(&self, other: &Bezier, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<f64> {
|
||||||
// TODO: Consider using the `intersections_between_vectors_of_curves` helper function here
|
// TODO: Consider using the `intersections_between_vectors_of_curves` helper function here
|
||||||
// Otherwise, use bounding box to determine intersections
|
// Otherwise, use bounding box to determine intersections
|
||||||
let mut intersection_t_values = self.unfiltered_intersections(other, error);
|
let mut intersection_t_values = self.unfiltered_intersections(other, error);
|
||||||
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
|
||||||
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
||||||
if !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_seperation.unwrap_or(MIN_SEPARATION_VALUE) {
|
if !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE) {
|
||||||
accumulator.pop();
|
accumulator.pop();
|
||||||
}
|
}
|
||||||
accumulator.push(*t);
|
accumulator.push(*t);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||||
/// Expects the following:
|
/// Expects the following:
|
||||||
/// - `other`: a [Bezier] curve to check intersections against
|
/// - `other`: a [Bezier] curve to check intersections against
|
||||||
/// - `error`: an optional f64 value to provide an error bound
|
/// - `error`: an optional f64 value to provide an error bound
|
||||||
/// - `minimum_seperation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
/// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
||||||
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two.
|
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/intersect-cubic/solo" title="Intersection Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/intersect-cubic/solo" title="Intersection Demo"></iframe>
|
||||||
pub fn intersections(&self, other: &Bezier, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
pub fn intersections(&self, other: &Bezier, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||||
|
|
@ -50,12 +50,12 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||||
|
|
||||||
/// Returns a list of `t` values that correspond to the self intersection points of the subpath. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
/// Returns a list of `t` values that correspond to the self intersection points of the subpath. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||||
/// - `minimum_seperation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
/// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
||||||
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two
|
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two
|
||||||
///
|
///
|
||||||
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
|
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/self-intersect/solo" title="Self-Intersection Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/self-intersect/solo" title="Self-Intersection Demo"></iframe>
|
||||||
pub fn self_intersections(&self, error: Option<f64>, minimum_seperation: Option<f64>) -> Vec<(usize, f64)> {
|
pub fn self_intersections(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||||
let mut intersections_vec = Vec::new();
|
let mut intersections_vec = Vec::new();
|
||||||
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
||||||
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
||||||
|
|
@ -64,7 +64,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||||
self.iter().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
self.iter().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
||||||
intersections_vec.extend(
|
intersections_vec.extend(
|
||||||
curve
|
curve
|
||||||
.intersections(&other, error, minimum_seperation)
|
.intersections(&other, error, minimum_separation)
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&value| value > &err && (1. - value) > err)
|
.filter(|&value| value > &err && (1. - value) > err)
|
||||||
.map(|value| (j, *value)),
|
.map(|value| (j, *value)),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { WasmBezier } from "@/../wasm/pkg";
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
||||||
import { renderDemo } from "@/utils/render";
|
import { renderDemo } from "@/utils/render";
|
||||||
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, TVariant, Demo } from "@/utils/types";
|
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, InputOption, WasmBezierManipulatorKey, Demo } from "@/utils/types";
|
||||||
|
|
||||||
const SELECTABLE_RANGE = 10;
|
const SELECTABLE_RANGE = 10;
|
||||||
|
|
||||||
|
|
@ -20,12 +20,10 @@ class BezierDemo extends HTMLElement implements Demo {
|
||||||
|
|
||||||
key!: BezierFeatureKey;
|
key!: BezierFeatureKey;
|
||||||
|
|
||||||
sliderOptions!: SliderOption[];
|
inputOptions!: InputOption[];
|
||||||
|
|
||||||
triggerOnMouseMove!: boolean;
|
triggerOnMouseMove!: boolean;
|
||||||
|
|
||||||
tVariant!: TVariant;
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
bezier!: WasmBezier;
|
bezier!: WasmBezier;
|
||||||
|
|
||||||
|
|
@ -39,33 +37,20 @@ class BezierDemo extends HTMLElement implements Demo {
|
||||||
|
|
||||||
sliderUnits!: Record<string, string | string[]>;
|
sliderUnits!: Record<string, string | string[]>;
|
||||||
|
|
||||||
static get observedAttributes(): string[] {
|
|
||||||
return ["tvariant"];
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
||||||
if (name === "tvariant" && oldValue) {
|
|
||||||
this.tVariant = (newValue || "Parametric") as TVariant;
|
|
||||||
const figure = this.querySelector("figure") as HTMLElement;
|
|
||||||
this.drawDemo(figure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectedCallback(): Promise<void> {
|
async connectedCallback(): Promise<void> {
|
||||||
this.title = this.getAttribute("title") || "";
|
this.title = this.getAttribute("title") || "";
|
||||||
this.points = JSON.parse(this.getAttribute("points") || "[]");
|
this.points = JSON.parse(this.getAttribute("points") || "[]");
|
||||||
this.key = this.getAttribute("key") as BezierFeatureKey;
|
this.key = this.getAttribute("key") as BezierFeatureKey;
|
||||||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]");
|
||||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||||
this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant;
|
|
||||||
|
|
||||||
this.callback = bezierFeatures[this.key].callback as BezierCallback;
|
this.callback = bezierFeatures[this.key].callback as BezierCallback;
|
||||||
const curveType = getCurveType(this.points.length);
|
const curveType = getCurveType(this.points.length);
|
||||||
|
|
||||||
this.manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
|
this.manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
|
||||||
this.activeIndex = undefined as number | undefined;
|
this.activeIndex = undefined as number | undefined;
|
||||||
this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
|
this.sliderData = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.default })));
|
||||||
this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit })));
|
this.sliderUnits = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.unit })));
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
const figure = this.querySelector("figure") as HTMLElement;
|
const figure = this.querySelector("figure") as HTMLElement;
|
||||||
|
|
@ -79,7 +64,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||||
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation, this.tVariant);
|
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent): void {
|
onMouseDown(event: MouseEvent): void {
|
||||||
|
|
@ -114,7 +99,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
||||||
|
|
||||||
getSliderUnit(sliderValue: number, variable: string): string {
|
getSliderUnit(sliderValue: number, variable: string): string {
|
||||||
const sliderUnit = this.sliderUnits[variable];
|
const sliderUnit = this.sliderUnits[variable];
|
||||||
return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || "";
|
return (Array.isArray(sliderUnit) ? "" : sliderUnit) || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
||||||
import { renderDemoPane } from "@/utils/render";
|
import { renderDemoPane } from "@/utils/render";
|
||||||
import { BezierCurveType, BEZIER_CURVE_TYPE, TVariant, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
|
import { BezierCurveType, BEZIER_CURVE_TYPE, BezierDemoOptions, InputOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
|
||||||
|
|
||||||
const demoDefaults = {
|
const demoDefaults = {
|
||||||
Linear: {
|
Linear: {
|
||||||
points: [
|
points: [
|
||||||
[30, 60],
|
[55, 60],
|
||||||
[140, 120],
|
[165, 120],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
points: [
|
points: [
|
||||||
[30, 50],
|
[55, 50],
|
||||||
[140, 30],
|
[165, 30],
|
||||||
[160, 170],
|
[185, 170],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Cubic: {
|
Cubic: {
|
||||||
points: [
|
points: [
|
||||||
[30, 30],
|
[55, 30],
|
||||||
[60, 140],
|
[85, 140],
|
||||||
[150, 30],
|
[175, 30],
|
||||||
[160, 160],
|
[185, 160],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -36,25 +36,19 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
||||||
|
|
||||||
triggerOnMouseMove!: boolean;
|
triggerOnMouseMove!: boolean;
|
||||||
|
|
||||||
chooseTVariant!: boolean;
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
demos!: BezierDemoArgs[];
|
demos!: BezierDemoArgs[];
|
||||||
|
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
tVariant!: TVariant;
|
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
this.tVariant = "Parametric";
|
|
||||||
this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
|
this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
|
||||||
this.id = `bezier/${this.key}`;
|
this.id = `bezier/${this.key}`;
|
||||||
this.name = bezierFeatures[this.key].name;
|
this.name = bezierFeatures[this.key].name;
|
||||||
this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]");
|
this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]");
|
||||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||||
this.chooseTVariant = this.getAttribute("chooseTVariant") === "true";
|
|
||||||
// Use quadratic slider options as a default if sliders are not provided for the other curve types.
|
// Use quadratic slider options as a default if sliders are not provided for the other curve types.
|
||||||
const defaultSliderOptions: SliderOption[] = this.demoOptions.Quadratic?.sliderOptions || [];
|
const defaultSliderOptions: InputOption[] = this.demoOptions.Quadratic?.inputOptions || [];
|
||||||
this.demos = BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => {
|
this.demos = BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => {
|
||||||
const givenData = this.demoOptions[curveType];
|
const givenData = this.demoOptions[curveType];
|
||||||
const defaultData = demoDefaults[curveType];
|
const defaultData = demoDefaults[curveType];
|
||||||
|
|
@ -62,7 +56,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
||||||
title: curveType,
|
title: curveType,
|
||||||
disabled: givenData?.disabled || false,
|
disabled: givenData?.disabled || false,
|
||||||
points: givenData?.customPoints || defaultData.points,
|
points: givenData?.customPoints || defaultData.points,
|
||||||
sliderOptions: givenData?.sliderOptions || defaultSliderOptions,
|
inputOptions: givenData?.inputOptions || defaultSliderOptions,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -77,9 +71,8 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
||||||
bezierDemo.setAttribute("title", demo.title);
|
bezierDemo.setAttribute("title", demo.title);
|
||||||
bezierDemo.setAttribute("points", JSON.stringify(demo.points));
|
bezierDemo.setAttribute("points", JSON.stringify(demo.points));
|
||||||
bezierDemo.setAttribute("key", this.key);
|
bezierDemo.setAttribute("key", this.key);
|
||||||
bezierDemo.setAttribute("sliderOptions", JSON.stringify(demo.sliderOptions));
|
bezierDemo.setAttribute("inputOptions", JSON.stringify(demo.inputOptions));
|
||||||
bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
||||||
bezierDemo.setAttribute("tvariant", this.tVariant);
|
|
||||||
return bezierDemo;
|
return bezierDemo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { WasmSubpath } from "@/../wasm/pkg";
|
||||||
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
||||||
import { renderDemo } from "@/utils/render";
|
import { renderDemo } from "@/utils/render";
|
||||||
|
|
||||||
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, TVariant } from "@/utils/types";
|
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, InputOption } from "@/utils/types";
|
||||||
|
|
||||||
const SELECTABLE_RANGE = 10;
|
const SELECTABLE_RANGE = 10;
|
||||||
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
|
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
|
||||||
|
|
@ -17,12 +17,10 @@ class SubpathDemo extends HTMLElement {
|
||||||
|
|
||||||
closed!: boolean;
|
closed!: boolean;
|
||||||
|
|
||||||
sliderOptions!: SliderOption[];
|
inputOptions!: InputOption[];
|
||||||
|
|
||||||
triggerOnMouseMove!: boolean;
|
triggerOnMouseMove!: boolean;
|
||||||
|
|
||||||
tVariant!: TVariant;
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
subpath!: WasmSubpath;
|
subpath!: WasmSubpath;
|
||||||
|
|
||||||
|
|
@ -36,30 +34,17 @@ class SubpathDemo extends HTMLElement {
|
||||||
|
|
||||||
sliderUnits!: Record<string, string | string[]>;
|
sliderUnits!: Record<string, string | string[]>;
|
||||||
|
|
||||||
static get observedAttributes(): string[] {
|
|
||||||
return ["tvariant"];
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
||||||
if (name === "tvariant" && oldValue) {
|
|
||||||
this.tVariant = (newValue || "Parametric") as TVariant;
|
|
||||||
const figure = this.querySelector("figure") as HTMLElement;
|
|
||||||
this.drawDemo(figure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectedCallback(): Promise<void> {
|
async connectedCallback(): Promise<void> {
|
||||||
this.title = this.getAttribute("title") || "";
|
this.title = this.getAttribute("title") || "";
|
||||||
this.triples = JSON.parse(this.getAttribute("triples") || "[]");
|
this.triples = JSON.parse(this.getAttribute("triples") || "[]");
|
||||||
this.key = this.getAttribute("key") as SubpathFeatureKey;
|
this.key = this.getAttribute("key") as SubpathFeatureKey;
|
||||||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]");
|
||||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||||
this.closed = this.getAttribute("closed") === "true";
|
this.closed = this.getAttribute("closed") === "true";
|
||||||
this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant;
|
|
||||||
|
|
||||||
this.callback = subpathFeatures[this.key].callback as SubpathCallback;
|
this.callback = subpathFeatures[this.key].callback as SubpathCallback;
|
||||||
this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
|
this.sliderData = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.default })));
|
||||||
this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit })));
|
this.sliderUnits = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.unit })));
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
const figure = this.querySelector("figure") as HTMLElement;
|
const figure = this.querySelector("figure") as HTMLElement;
|
||||||
|
|
@ -73,7 +58,7 @@ class SubpathDemo extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||||
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation, this.tVariant);
|
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent): void {
|
onMouseDown(event: MouseEvent): void {
|
||||||
|
|
@ -109,7 +94,7 @@ class SubpathDemo extends HTMLElement {
|
||||||
|
|
||||||
getSliderUnit(sliderValue: number, variable: string): string {
|
getSliderUnit(sliderValue: number, variable: string): string {
|
||||||
const sliderUnit = this.sliderUnits[variable];
|
const sliderUnit = this.sliderUnits[variable];
|
||||||
return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || "";
|
return (Array.isArray(sliderUnit) ? "" : sliderUnit) || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
||||||
import { renderDemoPane } from "@/utils/render";
|
import { renderDemoPane } from "@/utils/render";
|
||||||
import { TVariant, Demo, DemoPane, SliderOption, SubpathDemoArgs } from "@/utils/types";
|
import { Demo, DemoPane, InputOption, SubpathDemoArgs } from "@/utils/types";
|
||||||
|
|
||||||
class SubpathDemoPane extends HTMLElement implements DemoPane {
|
class SubpathDemoPane extends HTMLElement implements DemoPane {
|
||||||
// Props
|
// Props
|
||||||
|
|
@ -8,52 +8,46 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
||||||
|
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
sliderOptions!: SliderOption[];
|
inputOptions!: InputOption[];
|
||||||
|
|
||||||
triggerOnMouseMove!: boolean;
|
triggerOnMouseMove!: boolean;
|
||||||
|
|
||||||
chooseTVariant!: boolean;
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
demos!: SubpathDemoArgs[];
|
demos!: SubpathDemoArgs[];
|
||||||
|
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
tVariant!: TVariant;
|
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
this.demos = [
|
this.demos = [
|
||||||
{
|
{
|
||||||
title: "Open Subpath",
|
title: "Open Subpath",
|
||||||
triples: [
|
triples: [
|
||||||
[[20, 20], undefined, [10, 90]],
|
[[45, 20], undefined, [35, 90]],
|
||||||
[[150, 40], [60, 40], undefined],
|
[[175, 40], [85, 40], undefined],
|
||||||
[[175, 175], undefined, undefined],
|
[[200, 175], undefined, undefined],
|
||||||
[[100, 100], [40, 120], undefined],
|
[[125, 100], [65, 120], undefined],
|
||||||
],
|
],
|
||||||
closed: false,
|
closed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Closed Subpath",
|
title: "Closed Subpath",
|
||||||
triples: [
|
triples: [
|
||||||
[[35, 125], undefined, [40, 40]],
|
[[60, 125], undefined, [65, 40]],
|
||||||
[[130, 30], [120, 120], undefined],
|
[[155, 30], [145, 120], undefined],
|
||||||
[
|
[
|
||||||
[145, 150],
|
[170, 150],
|
||||||
[175, 90],
|
[200, 90],
|
||||||
[70, 185],
|
[95, 185],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
closed: true,
|
closed: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
this.tVariant = "Parametric";
|
|
||||||
this.key = (this.getAttribute("name") || "") as SubpathFeatureKey;
|
this.key = (this.getAttribute("name") || "") as SubpathFeatureKey;
|
||||||
this.id = `subpath/${this.key}`;
|
this.id = `subpath/${this.key}`;
|
||||||
this.name = subpathFeatures[this.key].name;
|
this.name = subpathFeatures[this.key].name;
|
||||||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]");
|
||||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||||
this.chooseTVariant = this.getAttribute("chooseTVariant") === "true";
|
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -68,9 +62,8 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
||||||
subpathDemo.setAttribute("triples", JSON.stringify(demo.triples));
|
subpathDemo.setAttribute("triples", JSON.stringify(demo.triples));
|
||||||
subpathDemo.setAttribute("closed", String(demo.closed));
|
subpathDemo.setAttribute("closed", String(demo.closed));
|
||||||
subpathDemo.setAttribute("key", this.key);
|
subpathDemo.setAttribute("key", this.key);
|
||||||
subpathDemo.setAttribute("sliderOptions", JSON.stringify(this.sliderOptions));
|
subpathDemo.setAttribute("inputOptions", JSON.stringify(this.inputOptions));
|
||||||
subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
||||||
subpathDemo.setAttribute("tvariant", this.tVariant);
|
|
||||||
return subpathDemo;
|
return subpathDemo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { WasmBezier } from "@/../wasm/pkg";
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
import { tSliderOptions, errorOptions, minimumSeparationOptions } from "@/utils/options";
|
import { tSliderOptions, bezierTValueVariantOptions, errorOptions, minimumSeparationOptions } from "@/utils/options";
|
||||||
import { TVariant, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types";
|
import { BezierDemoOptions, WasmBezierInstance, BezierCallback, InputOption, BEZIER_T_VALUE_VARIANTS } from "@/utils/types";
|
||||||
|
|
||||||
const bezierFeatures = {
|
const bezierFeatures = {
|
||||||
constructor: {
|
constructor: {
|
||||||
|
|
@ -26,7 +26,7 @@ const bezierFeatures = {
|
||||||
[120, 70],
|
[120, 70],
|
||||||
[160, 170],
|
[160, 170],
|
||||||
],
|
],
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
max: 0.99,
|
max: 0.99,
|
||||||
|
|
@ -42,7 +42,7 @@ const bezierFeatures = {
|
||||||
[120, 70],
|
[120, 70],
|
||||||
[160, 170],
|
[160, 170],
|
||||||
],
|
],
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
max: 0.99,
|
max: 0.99,
|
||||||
|
|
@ -67,20 +67,19 @@ const bezierFeatures = {
|
||||||
},
|
},
|
||||||
evaluate: {
|
evaluate: {
|
||||||
name: "Evaluate",
|
name: "Evaluate",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.evaluate(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.evaluate(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
"lookup-table": {
|
"lookup-table": {
|
||||||
name: "Lookup Table",
|
name: "Lookup Table",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 15,
|
max: 15,
|
||||||
|
|
@ -118,53 +117,53 @@ const bezierFeatures = {
|
||||||
},
|
},
|
||||||
tangent: {
|
tangent: {
|
||||||
name: "Tangent",
|
name: "Tangent",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.tangent(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.tangent(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
name: "Normal",
|
name: "Normal",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.normal(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.normal(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
curvature: {
|
curvature: {
|
||||||
name: "Curvature",
|
name: "Curvature",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.curvature(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.curvature(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Linear: {
|
Linear: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
|
},
|
||||||
|
Cubic: {
|
||||||
|
inputOptions: [bezierTValueVariantOptions, { ...tSliderOptions, default: 0.7 }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
split: {
|
split: {
|
||||||
name: "Split",
|
name: "Split",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.split(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.split(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
trim: {
|
trim: {
|
||||||
name: "Trim",
|
name: "Trim",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.trim(options.t1, options.t2, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.trim(options.t1, options.t2, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
|
bezierTValueVariantOptions,
|
||||||
{
|
{
|
||||||
variable: "t1",
|
variable: "t1",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -182,7 +181,6 @@ const bezierFeatures = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
name: "Project",
|
name: "Project",
|
||||||
|
|
@ -194,6 +192,9 @@ const bezierFeatures = {
|
||||||
name: "Local Extrema",
|
name: "Local Extrema",
|
||||||
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.local_extrema(),
|
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.local_extrema(),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
|
Linear: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[40, 40],
|
[40, 40],
|
||||||
|
|
@ -236,7 +237,7 @@ const bezierFeatures = {
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.offset(options.distance),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.offset(options.distance),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "distance",
|
variable: "distance",
|
||||||
min: -30,
|
min: -30,
|
||||||
|
|
@ -253,7 +254,7 @@ const bezierFeatures = {
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.outline(options.distance),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.outline(options.distance),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "distance",
|
variable: "distance",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -270,7 +271,7 @@ const bezierFeatures = {
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.graduated_outline(options.start_distance, options.end_distance),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.graduated_outline(options.start_distance, options.end_distance),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "start_distance",
|
variable: "start_distance",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -302,7 +303,7 @@ const bezierFeatures = {
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "distance1",
|
variable: "distance1",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -338,15 +339,13 @@ const bezierFeatures = {
|
||||||
arcs: {
|
arcs: {
|
||||||
name: "Arcs",
|
name: "Arcs",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.arcs(options.error, options.max_iterations, options.strategy),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.arcs(options.error, options.max_iterations, options.strategy),
|
||||||
demoOptions: ((): Omit<BezierDemoOptions, "Linear"> => {
|
demoOptions: ((): BezierDemoOptions => {
|
||||||
const sliderOptions = [
|
const inputOptions: InputOption[] = [
|
||||||
{
|
{
|
||||||
variable: "strategy",
|
variable: "strategy",
|
||||||
min: 0,
|
|
||||||
max: 2,
|
|
||||||
step: 1,
|
|
||||||
default: 0,
|
default: 0,
|
||||||
unit: [": Automatic", ": FavorLargerArcs", ": FavorCorrectness"],
|
inputType: "dropdown",
|
||||||
|
options: ["Automatic", "FavorLargerArcs", "FavorCorrectness"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
variable: "error",
|
variable: "error",
|
||||||
|
|
@ -365,13 +364,16 @@ const bezierFeatures = {
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Linear: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[50, 50],
|
[70, 40],
|
||||||
[85, 65],
|
[180, 50],
|
||||||
[100, 100],
|
[160, 150],
|
||||||
],
|
],
|
||||||
sliderOptions,
|
inputOptions,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
Cubic: {
|
Cubic: {
|
||||||
|
|
@ -381,7 +383,7 @@ const bezierFeatures = {
|
||||||
[30, 90],
|
[30, 90],
|
||||||
[180, 160],
|
[180, 160],
|
||||||
],
|
],
|
||||||
sliderOptions,
|
inputOptions,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -391,8 +393,8 @@ const bezierFeatures = {
|
||||||
name: "Intersect (Line Segment)",
|
name: "Intersect (Line Segment)",
|
||||||
callback: (bezier: WasmBezierInstance): string => {
|
callback: (bezier: WasmBezierInstance): string => {
|
||||||
const line = [
|
const line = [
|
||||||
[150, 150],
|
[45, 30],
|
||||||
[20, 20],
|
[195, 160],
|
||||||
];
|
];
|
||||||
return bezier.intersect_line_segment(line);
|
return bezier.intersect_line_segment(line);
|
||||||
},
|
},
|
||||||
|
|
@ -401,15 +403,15 @@ const bezierFeatures = {
|
||||||
name: "Intersect (Quadratic)",
|
name: "Intersect (Quadratic)",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
||||||
const quadratic = [
|
const quadratic = [
|
||||||
[20, 80],
|
[45, 80],
|
||||||
[180, 10],
|
[205, 10],
|
||||||
[90, 120],
|
[115, 120],
|
||||||
];
|
];
|
||||||
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_separation);
|
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_separation);
|
||||||
},
|
},
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [errorOptions, minimumSeparationOptions],
|
inputOptions: [errorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -417,16 +419,16 @@ const bezierFeatures = {
|
||||||
name: "Intersect (Cubic)",
|
name: "Intersect (Cubic)",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
||||||
const cubic = [
|
const cubic = [
|
||||||
[40, 20],
|
[65, 20],
|
||||||
[100, 40],
|
[125, 40],
|
||||||
[40, 120],
|
[65, 120],
|
||||||
[175, 140],
|
[200, 140],
|
||||||
];
|
];
|
||||||
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_separation);
|
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_separation);
|
||||||
},
|
},
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [errorOptions, minimumSeparationOptions],
|
inputOptions: [errorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -434,10 +436,14 @@ const bezierFeatures = {
|
||||||
name: "Intersect (Self)",
|
name: "Intersect (Self)",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
|
Linear: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [errorOptions],
|
disabled: true,
|
||||||
},
|
},
|
||||||
Cubic: {
|
Cubic: {
|
||||||
|
inputOptions: [errorOptions],
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[160, 180],
|
[160, 180],
|
||||||
[170, 10],
|
[170, 10],
|
||||||
|
|
@ -451,8 +457,8 @@ const bezierFeatures = {
|
||||||
name: "Intersect (Rectangle)",
|
name: "Intersect (Rectangle)",
|
||||||
callback: (bezier: WasmBezierInstance): string =>
|
callback: (bezier: WasmBezierInstance): string =>
|
||||||
bezier.intersect_rectangle([
|
bezier.intersect_rectangle([
|
||||||
[50, 50],
|
[75, 50],
|
||||||
[150, 150],
|
[175, 150],
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
|
|
@ -460,7 +466,7 @@ const bezierFeatures = {
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI, 100, 100),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI, 100, 100),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "angle",
|
variable: "angle",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -475,13 +481,12 @@ const bezierFeatures = {
|
||||||
},
|
},
|
||||||
"de-casteljau-points": {
|
"de-casteljau-points": {
|
||||||
name: "De Casteljau Points",
|
name: "De Casteljau Points",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.de_casteljau_points(options.t, tVariant),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.de_casteljau_points(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [bezierTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
join: {
|
join: {
|
||||||
name: "Join",
|
name: "Join",
|
||||||
|
|
@ -490,21 +495,21 @@ const bezierFeatures = {
|
||||||
let examplePoints = [];
|
let examplePoints = [];
|
||||||
if (points.length === 2) {
|
if (points.length === 2) {
|
||||||
examplePoints = [
|
examplePoints = [
|
||||||
[120, 155],
|
[145, 155],
|
||||||
[40, 155],
|
[65, 155],
|
||||||
];
|
];
|
||||||
} else if (points.length === 3) {
|
} else if (points.length === 3) {
|
||||||
examplePoints = [
|
examplePoints = [
|
||||||
[40, 150],
|
[65, 150],
|
||||||
[95, 195],
|
[120, 195],
|
||||||
[155, 145],
|
[190, 145],
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
examplePoints = [
|
examplePoints = [
|
||||||
[140, 150],
|
[165, 150],
|
||||||
[85, 110],
|
[110, 110],
|
||||||
[65, 180],
|
[90, 180],
|
||||||
[30, 140],
|
[55, 140],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return bezier.join(examplePoints);
|
return bezier.join(examplePoints);
|
||||||
|
|
@ -512,23 +517,23 @@ const bezierFeatures = {
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Linear: {
|
Linear: {
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[45, 40],
|
[70, 40],
|
||||||
[130, 90],
|
[155, 90],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[153, 40],
|
[185, 40],
|
||||||
[40, 20],
|
[65, 20],
|
||||||
[75, 85],
|
[100, 85],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Cubic: {
|
Cubic: {
|
||||||
customPoints: [
|
customPoints: [
|
||||||
[20, 80],
|
[45, 80],
|
||||||
[40, 20],
|
[65, 20],
|
||||||
[90, 100],
|
[115, 100],
|
||||||
[130, 55],
|
[155, 55],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -541,6 +546,5 @@ export type BezierFeatureOptions = {
|
||||||
callback: BezierCallback;
|
callback: BezierCallback;
|
||||||
demoOptions?: Partial<BezierDemoOptions>;
|
demoOptions?: Partial<BezierDemoOptions>;
|
||||||
triggerOnMouseMove?: boolean;
|
triggerOnMouseMove?: boolean;
|
||||||
chooseTVariant?: boolean;
|
|
||||||
};
|
};
|
||||||
export default bezierFeatures as Record<BezierFeatureKey, BezierFeatureOptions>;
|
export default bezierFeatures as Record<BezierFeatureKey, BezierFeatureOptions>;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { tSliderOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options";
|
import { tSliderOptions, subpathTValueVariantOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options";
|
||||||
import { TVariant, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types";
|
import { InputOption, SubpathCallback, WasmSubpathInstance, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types";
|
||||||
|
|
||||||
const subpathFeatures = {
|
const subpathFeatures = {
|
||||||
constructor: {
|
constructor: {
|
||||||
|
|
@ -8,9 +8,8 @@ const subpathFeatures = {
|
||||||
},
|
},
|
||||||
insert: {
|
insert: {
|
||||||
name: "Insert",
|
name: "Insert",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.insert(options.t, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.insert(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
length: {
|
length: {
|
||||||
name: "Length",
|
name: "Length",
|
||||||
|
|
@ -18,9 +17,8 @@ const subpathFeatures = {
|
||||||
},
|
},
|
||||||
evaluate: {
|
evaluate: {
|
||||||
name: "Evaluate",
|
name: "Evaluate",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.evaluate(options.t, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.evaluate(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
name: "Project",
|
name: "Project",
|
||||||
|
|
@ -30,15 +28,13 @@ const subpathFeatures = {
|
||||||
},
|
},
|
||||||
tangent: {
|
tangent: {
|
||||||
name: "Tangent",
|
name: "Tangent",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.tangent(options.t, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.tangent(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
name: "Normal",
|
name: "Normal",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.normal(options.t, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.normal(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
"local-extrema": {
|
"local-extrema": {
|
||||||
name: "Local Extrema",
|
name: "Local Extrema",
|
||||||
|
|
@ -57,67 +53,62 @@ const subpathFeatures = {
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
||||||
subpath.intersect_line_segment(
|
subpath.intersect_line_segment(
|
||||||
[
|
[
|
||||||
[150, 150],
|
[80, 30],
|
||||||
[20, 20],
|
[210, 150],
|
||||||
],
|
],
|
||||||
options.error,
|
options.error,
|
||||||
options.minimum_seperation
|
options.minimum_separation
|
||||||
),
|
),
|
||||||
sliderOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
"intersect-quadratic": {
|
"intersect-quadratic": {
|
||||||
name: "Intersect (Quadratic Segment)",
|
name: "Intersect (Quadratic Segment)",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
||||||
subpath.intersect_quadratic_segment(
|
subpath.intersect_quadratic_segment(
|
||||||
[
|
[
|
||||||
[20, 80],
|
[25, 50],
|
||||||
[180, 10],
|
[205, 10],
|
||||||
[90, 120],
|
[135, 180],
|
||||||
],
|
],
|
||||||
options.error,
|
options.error,
|
||||||
options.minimum_seperation
|
options.minimum_separation
|
||||||
),
|
),
|
||||||
sliderOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
"intersect-cubic": {
|
"intersect-cubic": {
|
||||||
name: "Intersect (Cubic Segment)",
|
name: "Intersect (Cubic Segment)",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string =>
|
||||||
subpath.intersect_cubic_segment(
|
subpath.intersect_cubic_segment(
|
||||||
[
|
[
|
||||||
[40, 20],
|
[65, 20],
|
||||||
[100, 40],
|
[125, 40],
|
||||||
[40, 120],
|
[65, 120],
|
||||||
[175, 140],
|
[200, 140],
|
||||||
],
|
],
|
||||||
options.error,
|
options.error,
|
||||||
options.minimum_seperation
|
options.minimum_separation
|
||||||
),
|
),
|
||||||
sliderOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
"self-intersect": {
|
"self-intersect": {
|
||||||
name: "Self Intersect",
|
name: "Self Intersect",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.self_intersections(options.error, options.minimum_seperation),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.self_intersections(options.error, options.minimum_separation),
|
||||||
sliderOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
|
||||||
},
|
},
|
||||||
split: {
|
split: {
|
||||||
name: "Split",
|
name: "Split",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.split(options.t, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.split(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
trim: {
|
trim: {
|
||||||
name: "Trim",
|
name: "Trim",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.trim(options.tVariant1, options.tVariant2, tVariant),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.trim(options.t1, options.t2, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
sliderOptions: [
|
inputOptions: [subpathTValueVariantOptions, { ...tSliderOptions, default: 0.2, variable: "t1" }, { ...tSliderOptions, variable: "t2" }],
|
||||||
{ ...tSliderOptions, default: 0.2, variable: "tVariant1" },
|
|
||||||
{ ...tSliderOptions, variable: "tVariant2" },
|
|
||||||
],
|
|
||||||
chooseTVariant: true,
|
|
||||||
},
|
},
|
||||||
offset: {
|
offset: {
|
||||||
name: "Offset",
|
name: "Offset",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.offset(options.distance),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.offset(options.distance),
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "distance",
|
variable: "distance",
|
||||||
min: -25,
|
min: -25,
|
||||||
|
|
@ -130,7 +121,7 @@ const subpathFeatures = {
|
||||||
outline: {
|
outline: {
|
||||||
name: "Outline",
|
name: "Outline",
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.outline(options.distance),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.outline(options.distance),
|
||||||
sliderOptions: [
|
inputOptions: [
|
||||||
{
|
{
|
||||||
variable: "distance",
|
variable: "distance",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -146,8 +137,7 @@ export type SubpathFeatureKey = keyof typeof subpathFeatures;
|
||||||
export type SubpathFeatureOptions = {
|
export type SubpathFeatureOptions = {
|
||||||
name: string;
|
name: string;
|
||||||
callback: SubpathCallback;
|
callback: SubpathCallback;
|
||||||
sliderOptions?: SliderOption[];
|
inputOptions?: InputOption[];
|
||||||
triggerOnMouseMove?: boolean;
|
triggerOnMouseMove?: boolean;
|
||||||
chooseTVariant?: boolean;
|
|
||||||
};
|
};
|
||||||
export default subpathFeatures as Record<SubpathFeatureKey, SubpathFeatureOptions>;
|
export default subpathFeatures as Record<SubpathFeatureKey, SubpathFeatureOptions>;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,12 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.document.title = "Bezier-rs Interactive Documentation";
|
window.document.title = "Bezier-rs Interactive Documentation";
|
||||||
|
window.document.head.innerHTML += `
|
||||||
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Bona+Nova:wght@700&family=EB+Garamond:ital,wght@0,500;1,500&display=swap" rel="stylesheet">
|
||||||
|
`.trim();
|
||||||
|
|
||||||
window.customElements.define("bezier-demo", BezierDemo);
|
window.customElements.define("bezier-demo", BezierDemo);
|
||||||
window.customElements.define("bezier-demo-pane", BezierDemoPane);
|
window.customElements.define("bezier-demo-pane", BezierDemoPane);
|
||||||
|
|
@ -31,7 +37,6 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement
|
||||||
demo.setAttribute("name", featureName);
|
demo.setAttribute("name", featureName);
|
||||||
demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {}));
|
demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {}));
|
||||||
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
||||||
demo.setAttribute("chooseTVariant", String(feature.chooseTVariant));
|
|
||||||
container?.append(demo);
|
container?.append(demo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,9 +45,8 @@ function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElemen
|
||||||
const demo = document.createElement("subpath-demo-pane");
|
const demo = document.createElement("subpath-demo-pane");
|
||||||
|
|
||||||
demo.setAttribute("name", featureName);
|
demo.setAttribute("name", featureName);
|
||||||
demo.setAttribute("sliderOptions", JSON.stringify(feature.sliderOptions || []));
|
demo.setAttribute("inputOptions", JSON.stringify(feature.inputOptions || []));
|
||||||
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
||||||
demo.setAttribute("chooseTVariant", String(feature.chooseTVariant));
|
|
||||||
container?.append(demo);
|
container?.append(demo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,16 +80,16 @@ function renderExamples(): void {
|
||||||
renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos"));
|
renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos"));
|
||||||
} else {
|
} else {
|
||||||
window.document.body.innerHTML = `
|
window.document.body.innerHTML = `
|
||||||
<h1>Bezier-rs Interactive Documentation</h1>
|
<h1 class="website-header">Bezier-rs Interactive Documentation</h1>
|
||||||
<p>
|
<p class="website-description">
|
||||||
This is the interactive documentation for the <a href="https://crates.io/crates/bezier-rs"><b>Bezier-rs</b></a> library. View the
|
This is the interactive documentation for the <a href="https://crates.io/crates/bezier-rs"><b>Bezier-rs</b></a> library. View the
|
||||||
<a href="https://docs.rs/bezier-rs/latest/bezier_rs">crate documentation</a>
|
<a href="https://docs.rs/bezier-rs/latest/bezier_rs">crate documentation</a>
|
||||||
for detailed function descriptions and API usage. Click and drag on the endpoints of the demo curves to visualize the various Bezier utilities and functions.
|
for detailed function descriptions and API usage. Click and drag on the endpoints of the demo curves to visualize the various Bezier utilities and functions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Beziers</h2>
|
<h2 class="class-header">Beziers</h2>
|
||||||
<div id="bezier-demos"></div>
|
<div id="bezier-demos"></div>
|
||||||
<h2>Subpaths</h2>
|
<h2 class="class-header">Subpaths</h2>
|
||||||
<div id="subpath-demos"></div>
|
<div id="subpath-demos"></div>
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,34 @@
|
||||||
|
:root {
|
||||||
|
--color-navy: #16323f;
|
||||||
|
--color-gray: #cccccc;
|
||||||
|
--range-fill-dark: var(--color-navy);
|
||||||
|
--range-fill-light: var(--color-gray);
|
||||||
|
--range-thumb-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.website-header {
|
||||||
|
color: var(--color-navy);
|
||||||
|
font-family: "Bona Nova", serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.website-description {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-header {
|
||||||
|
color: var(--color-navy);
|
||||||
|
font-family: "Bona Nova", serif;
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
body > h1 {
|
body > h1 {
|
||||||
margin: 40px 0;
|
margin: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +65,8 @@ body > h2 {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
|
font-family: "Bona Nova", serif;
|
||||||
|
color: var(--color-navy);
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-pane-header a {
|
.demo-pane-header a {
|
||||||
|
|
@ -64,17 +90,117 @@ body > h2 {
|
||||||
|
|
||||||
/* Demo styles */
|
/* Demo styles */
|
||||||
.demo-header {
|
.demo-header {
|
||||||
margin: 20px 0;
|
font-family: "Bona Nova", serif;
|
||||||
|
color: var(--color-navy);
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-figure {
|
.demo-figure {
|
||||||
width: 200px;
|
width: 250px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
margin-bottom: 20px;
|
margin: 10px;
|
||||||
border: solid 1px black;
|
border: solid 1px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.parent-slider-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
svg text {
|
svg text {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Slider Styles */
|
||||||
|
.slider-container {
|
||||||
|
/* width: fit-content; */
|
||||||
|
width: 250px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-label {
|
||||||
|
font-family: monospace;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
margin-right: 15px;
|
||||||
|
width: 250px;
|
||||||
|
height: 7px;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
border-radius: 5px;
|
||||||
|
background: linear-gradient(var(--range-fill-dark), var(--range-fill-dark)) 0 / calc(0.5 * var(--range-thumb-height) + var(--range-ratio) * (100% - var(--range-thumb-height))) var(--range-fill-light);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Thumb */
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: var(--range-thumb-height);
|
||||||
|
width: var(--range-thumb-height);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--range-fill-dark);
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: var(--range-thumb-height);
|
||||||
|
width: var(--range-thumb-height);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--range-fill-dark);
|
||||||
|
box-shadow: 0 0 2px 0 #555;
|
||||||
|
transition: background .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb:hover {
|
||||||
|
background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), var(--range-fill-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb:hover {
|
||||||
|
background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), var(--range-fill-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Track */
|
||||||
|
input[type=range]::-webkit-slider-runnable-track {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range]::-moz-range-track {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
background: none;
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select Styles */
|
||||||
|
select {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-container {
|
||||||
|
width: 250px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-input {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { BEZIER_T_VALUE_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types";
|
||||||
|
|
||||||
export const tSliderOptions = {
|
export const tSliderOptions = {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
|
|
@ -15,7 +17,7 @@ export const errorOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const minimumSeparationOptions = {
|
export const minimumSeparationOptions = {
|
||||||
variable: "minimum_seperation",
|
variable: "minimum_separation",
|
||||||
min: 0.001,
|
min: 0.001,
|
||||||
max: 0.25,
|
max: 0.25,
|
||||||
step: 0.001,
|
step: 0.001,
|
||||||
|
|
@ -29,3 +31,17 @@ export const intersectionErrorOptions = {
|
||||||
step: 0.0025,
|
step: 0.0025,
|
||||||
default: 0.02,
|
default: 0.02,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const bezierTValueVariantOptions = {
|
||||||
|
variable: "TVariant",
|
||||||
|
default: 0,
|
||||||
|
inputType: "dropdown",
|
||||||
|
options: BEZIER_T_VALUE_VARIANTS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const subpathTValueVariantOptions = {
|
||||||
|
variable: "TVariant",
|
||||||
|
default: 0,
|
||||||
|
inputType: "dropdown",
|
||||||
|
options: SUBPATH_T_VALUE_VARIANTS,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { TVariant, Demo, DemoPane, SliderOption } from "@/utils/types";
|
import { Demo, DemoPane, InputOption } from "@/utils/types";
|
||||||
|
|
||||||
export function renderDemo(demo: Demo): void {
|
export function renderDemo(demo: Demo): void {
|
||||||
const header = document.createElement("h4");
|
const header = document.createElement("h4");
|
||||||
|
|
@ -14,28 +14,72 @@ export function renderDemo(demo: Demo): void {
|
||||||
demo.append(header);
|
demo.append(header);
|
||||||
demo.append(figure);
|
demo.append(figure);
|
||||||
|
|
||||||
demo.sliderOptions.forEach((sliderOption: SliderOption) => {
|
const parentSliderContainer = document.createElement("div");
|
||||||
const sliderLabel = document.createElement("div");
|
parentSliderContainer.className = "parent-slider-container";
|
||||||
const sliderData = demo.sliderData[sliderOption.variable];
|
|
||||||
const sliderUnit = demo.getSliderUnit(sliderData, sliderOption.variable);
|
|
||||||
sliderLabel.className = "slider-label";
|
|
||||||
sliderLabel.innerText = `${sliderOption.variable} = ${sliderData}${sliderUnit}`;
|
|
||||||
demo.append(sliderLabel);
|
|
||||||
|
|
||||||
const sliderInput = document.createElement("input");
|
demo.inputOptions.forEach((inputOption: InputOption) => {
|
||||||
sliderInput.className = "slider-input";
|
const isDropdown = inputOption.inputType === "dropdown";
|
||||||
sliderInput.type = "range";
|
|
||||||
sliderInput.max = String(sliderOption.max);
|
const sliderContainer = document.createElement("div");
|
||||||
sliderInput.min = String(sliderOption.min);
|
sliderContainer.className = isDropdown ? "select-container" : "slider-container";
|
||||||
sliderInput.step = String(sliderOption.step);
|
|
||||||
sliderInput.value = String(sliderOption.default);
|
const sliderLabel = document.createElement("div");
|
||||||
sliderInput.addEventListener("input", (event: Event): void => {
|
const sliderData = demo.sliderData[inputOption.variable];
|
||||||
demo.sliderData[sliderOption.variable] = Number((event.target as HTMLInputElement).value);
|
const sliderUnit = demo.getSliderUnit(sliderData, inputOption.variable);
|
||||||
sliderLabel.innerText = `${sliderOption.variable} = ${demo.sliderData[sliderOption.variable]}${sliderUnit}`;
|
sliderLabel.className = "slider-label";
|
||||||
demo.drawDemo(figure);
|
sliderLabel.innerText = `${inputOption.variable}: ${isDropdown ? "" : sliderData}${sliderUnit}`;
|
||||||
});
|
sliderContainer.appendChild(sliderLabel);
|
||||||
demo.append(sliderInput);
|
|
||||||
|
if (isDropdown) {
|
||||||
|
const selectInput = document.createElement("select");
|
||||||
|
selectInput.className = "select-input";
|
||||||
|
selectInput.value = String(inputOption.default);
|
||||||
|
inputOption.options?.forEach((value, idx) => {
|
||||||
|
const id = `${idx}-${value}`;
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = String(idx);
|
||||||
|
option.id = id;
|
||||||
|
option.text = value;
|
||||||
|
selectInput.append(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
selectInput.addEventListener("change", (event: Event): void => {
|
||||||
|
demo.sliderData[inputOption.variable] = Number((event.target as HTMLInputElement).value);
|
||||||
|
demo.drawDemo(figure);
|
||||||
|
});
|
||||||
|
sliderContainer.appendChild(selectInput);
|
||||||
|
} else {
|
||||||
|
const sliderInput = document.createElement("input");
|
||||||
|
sliderInput.className = "slider-input";
|
||||||
|
sliderInput.type = "range";
|
||||||
|
sliderInput.max = String(inputOption.max);
|
||||||
|
sliderInput.min = String(inputOption.min);
|
||||||
|
sliderInput.step = String(inputOption.step);
|
||||||
|
sliderInput.value = String(inputOption.default);
|
||||||
|
const range = Number(inputOption.max) - Number(inputOption.min);
|
||||||
|
|
||||||
|
const ratio = (Number(inputOption.default) - Number(inputOption.min)) / range;
|
||||||
|
sliderInput.style.setProperty("--range-ratio", String(ratio));
|
||||||
|
|
||||||
|
sliderInput.addEventListener("input", (event: Event): void => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
demo.sliderData[inputOption.variable] = Number(target.value);
|
||||||
|
const data = demo.sliderData[inputOption.variable];
|
||||||
|
const unit = demo.getSliderUnit(demo.sliderData[inputOption.variable], inputOption.variable);
|
||||||
|
sliderLabel.innerText = `${inputOption.variable}: ${data}${unit}`;
|
||||||
|
|
||||||
|
const ratio = (Number(target.value) - Number(inputOption.min)) / range;
|
||||||
|
sliderInput.style.setProperty("--range-ratio", String(ratio));
|
||||||
|
|
||||||
|
demo.drawDemo(figure);
|
||||||
|
});
|
||||||
|
sliderContainer.appendChild(sliderInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentSliderContainer.append(sliderContainer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
demo.append(parentSliderContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderDemoPane(demoPane: DemoPane): void {
|
export function renderDemoPane(demoPane: DemoPane): void {
|
||||||
|
|
@ -55,30 +99,6 @@ export function renderDemoPane(demoPane: DemoPane): void {
|
||||||
container.append(header);
|
container.append(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tVariantContainer = document.createElement("div");
|
|
||||||
tVariantContainer.className = "t-variant-choice";
|
|
||||||
|
|
||||||
const tVariantLabel = document.createElement("strong");
|
|
||||||
tVariantLabel.innerText = "TValue Variant:";
|
|
||||||
tVariantContainer.append(tVariantLabel);
|
|
||||||
|
|
||||||
const radioInputs = ["Parametric", "Euclidean"].map((tVariant) => {
|
|
||||||
const id = `${demoPane.id}-${tVariant}`;
|
|
||||||
const radioInput = document.createElement("input");
|
|
||||||
radioInput.type = "radio";
|
|
||||||
radioInput.id = id;
|
|
||||||
radioInput.value = tVariant;
|
|
||||||
radioInput.name = `TVariant - ${demoPane.id}`;
|
|
||||||
radioInput.checked = tVariant === "Parametric";
|
|
||||||
tVariantContainer.append(radioInput);
|
|
||||||
|
|
||||||
const label = document.createElement("label");
|
|
||||||
label.htmlFor = id;
|
|
||||||
label.innerText = tVariant;
|
|
||||||
tVariantContainer.append(label);
|
|
||||||
return radioInput;
|
|
||||||
});
|
|
||||||
|
|
||||||
const demoRow = document.createElement("div");
|
const demoRow = document.createElement("div");
|
||||||
demoRow.className = "demo-row";
|
demoRow.className = "demo-row";
|
||||||
|
|
||||||
|
|
@ -87,21 +107,9 @@ export function renderDemoPane(demoPane: DemoPane): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const demoComponent = demoPane.buildDemo(demo);
|
const demoComponent = demoPane.buildDemo(demo);
|
||||||
|
|
||||||
radioInputs.forEach((radioInput: HTMLElement) => {
|
|
||||||
radioInput.addEventListener("input", (event: Event): void => {
|
|
||||||
demoPane.tVariant = (event.target as HTMLInputElement).value as TVariant;
|
|
||||||
demoComponent.setAttribute("tvariant", demoPane.tVariant);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
demoRow.append(demoComponent);
|
demoRow.append(demoComponent);
|
||||||
});
|
});
|
||||||
|
|
||||||
container.append(demoRow);
|
container.append(demoRow);
|
||||||
|
|
||||||
if (demoPane.chooseTVariant) {
|
|
||||||
container.append(tVariantContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
demoPane.append(container);
|
demoPane.append(container);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,26 @@ export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_ou
|
||||||
export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const;
|
export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const;
|
||||||
export type BezierCurveType = typeof BEZIER_CURVE_TYPE[number];
|
export type BezierCurveType = typeof BEZIER_CURVE_TYPE[number];
|
||||||
|
|
||||||
export type TVariant = "Euclidean" | "Parametric";
|
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string;
|
||||||
|
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string;
|
||||||
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number], tVariant?: TVariant) => string;
|
|
||||||
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number], tVariant?: TVariant) => string;
|
|
||||||
|
|
||||||
export type BezierDemoOptions = {
|
export type BezierDemoOptions = {
|
||||||
[key in BezierCurveType]: {
|
[key in BezierCurveType]: {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
sliderOptions?: SliderOption[];
|
inputOptions?: InputOption[];
|
||||||
customPoints?: number[][];
|
customPoints?: number[][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SliderOption = {
|
export type InputOption = {
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
step: number;
|
|
||||||
default: number;
|
|
||||||
variable: string;
|
variable: string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
default?: number;
|
||||||
unit?: string | string[];
|
unit?: string | string[];
|
||||||
|
inputType?: "slider" | "dropdown";
|
||||||
|
options?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getCurveType(numPoints: number): BezierCurveType {
|
export function getCurveType(numPoints: number): BezierCurveType {
|
||||||
|
|
@ -61,7 +61,7 @@ export interface DemoArgs {
|
||||||
|
|
||||||
export interface BezierDemoArgs extends DemoArgs {
|
export interface BezierDemoArgs extends DemoArgs {
|
||||||
points: number[][];
|
points: number[][];
|
||||||
sliderOptions: SliderOption[];
|
inputOptions: InputOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubpathDemoArgs extends DemoArgs {
|
export interface SubpathDemoArgs extends DemoArgs {
|
||||||
|
|
@ -70,7 +70,7 @@ export interface SubpathDemoArgs extends DemoArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Demo extends HTMLElement {
|
export interface Demo extends HTMLElement {
|
||||||
sliderOptions: SliderOption[];
|
inputOptions: InputOption[];
|
||||||
sliderData: Record<string, number>;
|
sliderData: Record<string, number>;
|
||||||
sliderUnits: Record<string, string | string[]>;
|
sliderUnits: Record<string, string | string[]>;
|
||||||
|
|
||||||
|
|
@ -85,7 +85,8 @@ export interface DemoPane extends HTMLElement {
|
||||||
name: string;
|
name: string;
|
||||||
demos: DemoArgs[];
|
demos: DemoArgs[];
|
||||||
id: string;
|
id: string;
|
||||||
chooseTVariant: boolean;
|
|
||||||
tVariant: TVariant;
|
|
||||||
buildDemo(demo: DemoArgs): Demo;
|
buildDemo(demo: DemoArgs): Demo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const;
|
||||||
|
export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ const SCALE_UNIT_VECTOR_FACTOR: f64 = 50.;
|
||||||
|
|
||||||
fn parse_t_variant(t_variant: &String, t: f64) -> SubpathTValue {
|
fn parse_t_variant(t_variant: &String, t: f64) -> SubpathTValue {
|
||||||
match t_variant.as_str() {
|
match t_variant.as_str() {
|
||||||
"Parametric" => SubpathTValue::GlobalParametric(t),
|
"GlobalParametric" => SubpathTValue::GlobalParametric(t),
|
||||||
"Euclidean" => SubpathTValue::GlobalEuclidean(t),
|
"GlobalEuclidean" => SubpathTValue::GlobalEuclidean(t),
|
||||||
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
// SVG drawing constants
|
// SVG drawing constants
|
||||||
pub const SVG_OPEN_TAG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px">"#;
|
pub const SVG_OPEN_TAG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="250px" height="200px">"#;
|
||||||
pub const SVG_CLOSE_TAG: &str = "</svg>";
|
pub const SVG_CLOSE_TAG: &str = "</svg>";
|
||||||
|
|
||||||
// Stylistic constants
|
// Stylistic constants
|
||||||
|
|
@ -30,7 +30,7 @@ pub fn wrap_svg_tag(contents: String) -> String {
|
||||||
|
|
||||||
/// Helper function to create an SVG text entity.
|
/// Helper function to create an SVG text entity.
|
||||||
pub fn draw_text(text: String, x_pos: f64, y_pos: f64, fill: &str) -> String {
|
pub fn draw_text(text: String, x_pos: f64, y_pos: f64, fill: &str) -> String {
|
||||||
format!(r#"<text x="{x_pos}" y="{y_pos}" fill="{fill}">{text}</text>"#)
|
format!(r#"<text x="{x_pos}" y="{y_pos}" fill="{fill}" font-family="monospace">{text}</text>"#)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to create an SVG circle entity.
|
/// Helper function to create an SVG circle entity.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue