import { WasmBezier } from "@/../wasm/pkg"; import type { BezierDemoOptions, WasmBezierInstance, BezierCallback, InputOption } from "@/types"; import { capOptions, tSliderOptions, bezierTValueVariantOptions, errorOptions, minimumSeparationOptions, BEZIER_T_VALUE_VARIANTS } from "@/types"; const bezierFeatures = { constructor: { name: "Constructor", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.to_svg(), }, "bezier-through-points": { name: "Bezier Through Points", callback: (bezier: WasmBezierInstance, options: Record): string => { const points = bezier.get_points(); if (Object.values(options).length === 1) { return WasmBezier.quadratic_through_points(points, options.t); } return WasmBezier.cubic_through_points(points, options.t, options["midpoint separation"]); }, demoOptions: { Linear: { disabled: true, }, Quadratic: { customPoints: [ [30, 50], [120, 70], [160, 170], ], inputOptions: [ { variable: "t", inputType: "slider", min: 0.01, max: 0.99, step: 0.01, default: 0.5, }, ], }, Cubic: { customPoints: [ [30, 50], [120, 70], [160, 170], ], inputOptions: [ { variable: "t", inputType: "slider", min: 0.01, max: 0.99, step: 0.01, default: 0.5, }, { variable: "midpoint separation", inputType: "slider", min: 0, max: 100, step: 2, default: 30, }, ], }, }, }, length: { name: "Length", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.length(), }, "length-centroid": { name: "Length Centroid", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.length_centroid(), }, evaluate: { name: "Evaluate", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.evaluate(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, }, "lookup-table": { name: "Lookup Table", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.compute_lookup_table(options.steps, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [ bezierTValueVariantOptions, { variable: "steps", inputType: "slider", min: 2, max: 15, step: 1, default: 5, }, ], }, }, }, derivative: { name: "Derivative", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.derivative(), demoOptions: { Linear: { disabled: true, }, Quadratic: { customPoints: [ [30, 40], [110, 50], [120, 130], ], }, Cubic: { customPoints: [ [50, 50], [60, 100], [100, 140], [140, 150], ], }, }, }, tangent: { name: "Tangent", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.tangent(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, }, "tangents-to-point": { name: "Tangents To Point", callback: (bezier: WasmBezierInstance, _: Record, mouseLocation?: [number, number]): string => mouseLocation ? bezier.tangents_to_point(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(), triggerOnMouseMove: true, demoOptions: { Linear: { disabled: true } }, }, normal: { name: "Normal", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.normal(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, }, "normals-to-point": { name: "Normals To Point", callback: (bezier: WasmBezierInstance, _: Record, mouseLocation?: [number, number]): string => mouseLocation ? bezier.normals_to_point(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(), triggerOnMouseMove: true, }, curvature: { name: "Curvature", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.curvature(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Linear: { disabled: true, }, Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, Cubic: { inputOptions: [bezierTValueVariantOptions, { ...tSliderOptions, default: 0.7 }], }, }, }, split: { name: "Split", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.split(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, }, trim: { name: "Trim", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.trim(options.t1, options.t2, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [ bezierTValueVariantOptions, { variable: "t1", inputType: "slider", min: 0, max: 1, step: 0.01, default: 0.25, }, { variable: "t2", inputType: "slider", min: 0, max: 1, step: 0.01, default: 0.75, }, ], }, }, }, project: { name: "Project", callback: (bezier: WasmBezierInstance, _: Record, mouseLocation?: [number, number]): string => mouseLocation ? bezier.project(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(), triggerOnMouseMove: true, }, "local-extrema": { name: "Local Extrema", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.local_extrema(), demoOptions: { Linear: { disabled: true, }, Quadratic: { customPoints: [ [40, 40], [160, 30], [110, 150], ], }, Cubic: { customPoints: [ [160, 180], [170, 10], [30, 90], [180, 160], ], }, }, }, "bounding-box": { name: "Bounding Box", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.bounding_box(), }, inflections: { name: "Inflections", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.inflections(), demoOptions: { Linear: { disabled: true, }, Quadratic: { disabled: true, }, }, }, reduce: { name: "Reduce", callback: (bezier: WasmBezierInstance): string => bezier.reduce(), }, offset: { name: "Offset", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.offset(options.distance), demoOptions: { Quadratic: { inputOptions: [ { variable: "distance", inputType: "slider", min: -30, max: 30, step: 1, default: 15, }, ], }, }, }, outline: { name: "Outline", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.outline(options.distance, options.cap), demoOptions: { Quadratic: { inputOptions: [ { variable: "distance", inputType: "slider", min: 0, max: 30, step: 1, default: 15, }, capOptions, ], }, }, }, "graduated-outline": { name: "Graduated Outline", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.graduated_outline(options.start_distance, options.end_distance, options.cap), demoOptions: { Quadratic: { inputOptions: [ { variable: "start_distance", inputType: "slider", min: 0, max: 30, step: 1, default: 5, }, { variable: "end_distance", inputType: "slider", min: 0, max: 30, step: 1, default: 15, }, capOptions, ], }, }, customPoints: { Cubic: [ [31, 94], [40, 40], [107, 107], [106, 106], ], }, }, "skewed-outline": { name: "Skewed Outline", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4, options.cap), demoOptions: { Quadratic: { inputOptions: [ { variable: "distance1", inputType: "slider", min: 0, max: 30, step: 1, default: 20, }, { variable: "distance2", inputType: "slider", min: 0, max: 30, step: 1, default: 10, }, { variable: "distance3", inputType: "slider", min: 0, max: 30, step: 1, default: 30, }, { variable: "distance4", inputType: "slider", min: 0, max: 30, step: 1, default: 5, }, capOptions, ], }, }, }, arcs: { name: "Arcs", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.arcs(options.error, options.max_iterations, options.strategy), demoOptions: ((): BezierDemoOptions => { const inputOptions: InputOption[] = [ { variable: "strategy", inputType: "dropdown", default: 0, options: ["Automatic", "FavorLargerArcs", "FavorCorrectness"], }, { variable: "error", inputType: "slider", min: 0.05, max: 1, step: 0.05, default: 0.5, }, { variable: "max_iterations", inputType: "slider", min: 50, max: 200, step: 1, default: 100, }, ]; return { Linear: { disabled: true, }, Quadratic: { customPoints: [ [70, 40], [180, 50], [160, 150], ], inputOptions, disabled: false, }, Cubic: { customPoints: [ [160, 180], [170, 10], [30, 90], [180, 160], ], inputOptions, disabled: false, }, }; })(), }, "intersect-linear": { name: "Intersect (Linear Segment)", callback: (bezier: WasmBezierInstance): string => { const line = [ [45, 30], [195, 160], ]; return bezier.intersect_line_segment(line); }, }, "intersect-quadratic": { name: "Intersect (Quadratic Segment)", callback: (bezier: WasmBezierInstance, options: Record): string => { const quadratic = [ [45, 80], [205, 10], [115, 120], ]; return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_separation); }, demoOptions: { Quadratic: { inputOptions: [errorOptions, minimumSeparationOptions], }, }, }, "intersect-cubic": { name: "Intersect (Cubic Segment)", callback: (bezier: WasmBezierInstance, options: Record): string => { const cubic = [ [65, 20], [125, 40], [65, 120], [200, 140], ]; return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_separation); }, demoOptions: { Quadratic: { inputOptions: [errorOptions, minimumSeparationOptions], }, }, }, "intersect-self": { name: "Intersect (Self)", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.intersect_self(options.error, options.minimum_separation), demoOptions: { Linear: { disabled: true, }, Quadratic: { disabled: true, }, Cubic: { inputOptions: [errorOptions, minimumSeparationOptions], customPoints: [ [160, 180], [170, 10], [30, 90], [180, 140], ], }, }, }, "intersect-rectangle": { name: "Intersect (Rectangle)", callback: (bezier: WasmBezierInstance): string => bezier.intersect_rectangle([ [75, 50], [175, 150], ]), }, rotate: { name: "Rotate", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.rotate(options.angle * Math.PI, 125, 100), demoOptions: { Quadratic: { inputOptions: [ { variable: "angle", inputType: "slider", min: 0, max: 2, step: 1 / 50, default: 0.12, unit: "π", }, ], }, }, }, "de-casteljau-points": { name: "De Casteljau Points", callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.de_casteljau_points(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, }, join: { name: "Join", callback: (bezier: WasmBezierInstance): string => { const points = bezier.get_points(); let examplePoints = []; if (points.length === 2) { examplePoints = [ [145, 155], [65, 155], ]; } else if (points.length === 3) { examplePoints = [ [65, 150], [120, 195], [190, 145], ]; } else { examplePoints = [ [165, 150], [110, 110], [90, 180], [55, 140], ]; } return bezier.join(examplePoints); }, demoOptions: { Linear: { customPoints: [ [70, 40], [155, 90], ], }, Quadratic: { customPoints: [ [185, 40], [65, 20], [100, 85], ], }, Cubic: { customPoints: [ [45, 80], [65, 20], [115, 100], [155, 55], ], }, }, }, }; export type BezierFeatureKey = keyof typeof bezierFeatures; export type BezierFeatureOptions = { name: string; callback: BezierCallback; demoOptions?: Partial; triggerOnMouseMove?: boolean; }; export default bezierFeatures as Record;