diff --git a/excalidraw-app/components/DebugCanvas.tsx b/excalidraw-app/components/DebugCanvas.tsx index 7243551ab..eb429b089 100644 --- a/excalidraw-app/components/DebugCanvas.tsx +++ b/excalidraw-app/components/DebugCanvas.tsx @@ -12,9 +12,10 @@ import { TrashIcon, } from "../../packages/excalidraw/components/icons"; import { STORAGE_KEYS } from "../app_constants"; -import type { Arc } from "../../packages/math"; +import type { Arc, CubicBezier } from "../../packages/math"; import { isArc, + isBezier, isSegment, type GlobalPoint, type Segment, @@ -35,6 +36,28 @@ const renderLine = ( context.restore(); }; +const renderCubicBezier = ( + context: CanvasRenderingContext2D, + zoom: number, + { start, control1, control2, end }: CubicBezier, + color: string, +) => { + context.save(); + context.strokeStyle = color; + context.beginPath(); + context.moveTo(start[0] * zoom, start[1] * zoom); + context.bezierCurveTo( + control1[0] * zoom, + control1[1] * zoom, + control2[0] * zoom, + control2[1] * zoom, + end[0] * zoom, + end[1] * zoom, + ); + context.stroke(); + context.restore(); +}; + const renderArc = ( context: CanvasRenderingContext2D, zoom: number, @@ -90,6 +113,16 @@ const render = ( el.color, ); break; + case isBezier(el.data): + renderCubicBezier( + context, + appState.zoom.value, + el.data as CubicBezier, + el.color, + ); + break; + default: + throw new Error("Unknown element type"); } }); }; diff --git a/packages/excalidraw/visualdebug.ts b/packages/excalidraw/visualdebug.ts index 3213b9de1..f64697ac2 100644 --- a/packages/excalidraw/visualdebug.ts +++ b/packages/excalidraw/visualdebug.ts @@ -1,4 +1,4 @@ -import type { Arc, Segment } from "../math"; +import type { Arc, CubicBezier, Segment } from "../math"; import { isSegment, segment, pointFrom, type GlobalPoint } from "../math"; import { isBounds } from "./element/typeChecks"; import type { Bounds } from "./element/types"; @@ -15,10 +15,24 @@ declare global { export type DebugElement = { color: string; - data: Segment | Arc; + data: Segment | Arc | CubicBezier; permanent: boolean; }; +export const debugDrawCubicBezier = ( + c: CubicBezier, + opts?: { + color?: string; + permanent?: boolean; + }, +) => { + addToCurrentFrame({ + color: opts?.color ?? "purple", + permanent: !!opts?.permanent, + data: c, + }); +}; + export const debugDrawArc = ( a: Arc, opts?: { diff --git a/packages/math/curve.ts b/packages/math/curve.ts index bc0c6582d..2afd47e6b 100644 --- a/packages/math/curve.ts +++ b/packages/math/curve.ts @@ -1,5 +1,5 @@ -import { pointFrom, pointRotateRads } from "./point"; -import type { Curve, GenericPoint, Radians } from "./types"; +import { isPoint, pointFrom, pointRotateRads } from "./point"; +import type { CubicBezier, Curve, GenericPoint, Radians } from "./types"; /** * @@ -221,3 +221,20 @@ const findClosestParameter = ( return closestT; }; + +export const isBezier = ( + c: unknown, +): c is CubicBezier => { + return ( + c != null && + typeof c === "object" && + Object.hasOwn(c, "start") && + Object.hasOwn(c, "end") && + Object.hasOwn(c, "control1") && + Object.hasOwn(c, "control2") && + isPoint((c as CubicBezier).start) && + isPoint((c as CubicBezier).end) && + isPoint((c as CubicBezier).control1) && + isPoint((c as CubicBezier).control2) + ); +}; diff --git a/packages/math/types.ts b/packages/math/types.ts index ecc42ffd2..93b59378d 100644 --- a/packages/math/types.ts +++ b/packages/math/types.ts @@ -132,9 +132,11 @@ export type Extent = { _brand: "excalimath_extent"; }; -// an ellipse is specified by its center, angle, and its major and minor axes -// but for the sake of simplicity, we've used halfWidth and halfHeight instead -// in replace of semi major and semi minor axes +/** + An ellipse is specified by its center, angle, and its major and minor axes + but for the sake of simplicity, we've used halfWidth and halfHeight instead + in replace of semi major and semi minor axes + */ export type Ellipse = { center: Point; halfWidth: number; @@ -142,3 +144,14 @@ export type Ellipse = { } & { _brand: "excalimath_ellipse"; }; + +/** + * Represents a cubic bezier with 2 control points on the point space of your + * choosing. + */ +export type CubicBezier

= { + start: P; + end: P; + control1: P; + control2: P; +};