Add cubic bezier curve visual debug

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs 2025-01-14 10:26:35 +01:00
parent 77ea537c69
commit 028c397c0a
No known key found for this signature in database
4 changed files with 85 additions and 8 deletions

View file

@ -12,9 +12,10 @@ import {
TrashIcon, TrashIcon,
} from "../../packages/excalidraw/components/icons"; } from "../../packages/excalidraw/components/icons";
import { STORAGE_KEYS } from "../app_constants"; import { STORAGE_KEYS } from "../app_constants";
import type { Arc } from "../../packages/math"; import type { Arc, CubicBezier } from "../../packages/math";
import { import {
isArc, isArc,
isBezier,
isSegment, isSegment,
type GlobalPoint, type GlobalPoint,
type Segment, type Segment,
@ -35,6 +36,28 @@ const renderLine = (
context.restore(); context.restore();
}; };
const renderCubicBezier = (
context: CanvasRenderingContext2D,
zoom: number,
{ start, control1, control2, end }: CubicBezier<GlobalPoint>,
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 = ( const renderArc = (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
zoom: number, zoom: number,
@ -90,6 +113,16 @@ const render = (
el.color, el.color,
); );
break; break;
case isBezier(el.data):
renderCubicBezier(
context,
appState.zoom.value,
el.data as CubicBezier<GlobalPoint>,
el.color,
);
break;
default:
throw new Error("Unknown element type");
} }
}); });
}; };

View file

@ -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 { isSegment, segment, pointFrom, type GlobalPoint } from "../math";
import { isBounds } from "./element/typeChecks"; import { isBounds } from "./element/typeChecks";
import type { Bounds } from "./element/types"; import type { Bounds } from "./element/types";
@ -15,10 +15,24 @@ declare global {
export type DebugElement = { export type DebugElement = {
color: string; color: string;
data: Segment<GlobalPoint> | Arc<GlobalPoint>; data: Segment<GlobalPoint> | Arc<GlobalPoint> | CubicBezier<GlobalPoint>;
permanent: boolean; permanent: boolean;
}; };
export const debugDrawCubicBezier = (
c: CubicBezier<GlobalPoint>,
opts?: {
color?: string;
permanent?: boolean;
},
) => {
addToCurrentFrame({
color: opts?.color ?? "purple",
permanent: !!opts?.permanent,
data: c,
});
};
export const debugDrawArc = ( export const debugDrawArc = (
a: Arc<GlobalPoint>, a: Arc<GlobalPoint>,
opts?: { opts?: {

View file

@ -1,5 +1,5 @@
import { pointFrom, pointRotateRads } from "./point"; import { isPoint, pointFrom, pointRotateRads } from "./point";
import type { Curve, GenericPoint, Radians } from "./types"; import type { CubicBezier, Curve, GenericPoint, Radians } from "./types";
/** /**
* *
@ -221,3 +221,20 @@ const findClosestParameter = <Point extends GenericPoint>(
return closestT; return closestT;
}; };
export const isBezier = <Point extends GenericPoint>(
c: unknown,
): c is CubicBezier<Point> => {
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<Point>).start) &&
isPoint((c as CubicBezier<Point>).end) &&
isPoint((c as CubicBezier<Point>).control1) &&
isPoint((c as CubicBezier<Point>).control2)
);
};

View file

@ -132,9 +132,11 @@ export type Extent = {
_brand: "excalimath_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 An ellipse is specified by its center, angle, and its major and minor axes
// in replace of semi major and semi 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<Point extends GenericPoint> = { export type Ellipse<Point extends GenericPoint> = {
center: Point; center: Point;
halfWidth: number; halfWidth: number;
@ -142,3 +144,14 @@ export type Ellipse<Point extends GenericPoint> = {
} & { } & {
_brand: "excalimath_ellipse"; _brand: "excalimath_ellipse";
}; };
/**
* Represents a cubic bezier with 2 control points on the point space of your
* choosing.
*/
export type CubicBezier<P extends GenericPoint> = {
start: P;
end: P;
control1: P;
control2: P;
};