diff --git a/packages/element/src/freedraw.ts b/packages/element/src/freedraw.ts index 1f317304fe..e490be0a28 100644 --- a/packages/element/src/freedraw.ts +++ b/packages/element/src/freedraw.ts @@ -1,7 +1,10 @@ import { + type GlobalPoint, type LineSegment, lineSegment, + lineSegmentIntersectionPoints, type LocalPoint, + pointDistance, pointFrom, pointFromVector, vectorAntiNormal, @@ -12,69 +15,148 @@ import { } from "@excalidraw/math"; import { type ExcalidrawFreeDrawElement } from "./types"; +import { doLineSegmentsIntersect } from "@excalidraw/utils"; +import { debugDrawLine, debugDrawPoint, distance } from "@excalidraw/common"; + +const offset = ( + x: number, + y: number, + pressure: number, + direction: "left" | "right", + origin: LocalPoint, +) => { + const p = pointFrom(x, y); + const v = vectorNormalize(vectorFromPoint(p, origin)); + const normal = direction === "left" ? vectorNormal(v) : vectorAntiNormal(v); + const scaled = vectorScale(normal, pressure / 2); + + return pointFromVector(scaled, origin); +}; function generateSegments( input: | readonly [x: number, y: number, pressure: number][] | readonly [x: number, y: number][], + element: ExcalidrawFreeDrawElement, ): LineSegment[] { - if (input.length < 2) { + if (input.length < 3) { return []; } - const offset = ( - x: number, - y: number, - pressure: number, - direction: "left" | "right", - ) => { - const p = pointFrom(x, y); - const v = vectorNormalize(vectorFromPoint(p)); - const normal = direction === "left" ? vectorAntiNormal(v) : vectorNormal(v); - const scaled = vectorScale(normal, pressure / 2); - - return pointFromVector(scaled, p); - }; - let idx = 0; - const segments = Array(input.length * 2 - 1); + //const segments: LineSegment[] = []; + const segments = Array(input.length * 4 - 4); segments[idx++] = lineSegment( - offset(input[0][0], input[0][1], input[0][2] ?? 5, "left"), - offset(input[1][0], input[1][1], input[0][2] ?? 5, "left"), - ); - - for (let i = 2; i < input.length; i++) { - const point = input[i]; - const prev = segments[idx - 1][1]; - - segments[idx++] = lineSegment( - prev, - offset(point[0], point[1], point[2] ?? 5, "left"), - ); - } - - const prev = segments[idx - 1][1]; - segments[idx++] = lineSegment( - prev, offset( - input[input.length - 1][0], - input[input.length - 1][1], - input[input.length - 1][2] ?? 5, + input[1][0], + input[1][1], + input[1][2] ?? 5, + "left", + pointFrom(input[0][0], input[0][1]), + ), + offset( + input[0][0], + input[0][1], + input[0][2] ?? 5, "right", + pointFrom(input[1][0], input[1][1]), ), ); - for (let i = input.length - 2; i >= 0; i--) { - const point = input[i]; - const prev = segments[idx - 1][1]; - - segments[idx++] = lineSegment( - prev, - offset(point[0], point[1], point[2] ?? 5, "right"), + for (let i = 2; i < input.length; i++) { + const a = segments[idx - 1][1]; + const b = offset( + input[i][0], + input[i][1], + input[i][2] ?? 5, + "left", + pointFrom(input[i - 1][0], input[i - 1][1]), ); + const c = offset( + input[i - 1][0], + input[i - 1][1], + input[i - 1][2] ?? 5, + "right", + pointFrom(input[i][0], input[i][1]), + ); + + segments[idx++] = lineSegment(a, b); // Bridge segment + segments[idx++] = lineSegment(b, c); // Main segment } + // Turnaround segments + const prev = segments[idx - 1][1]; + segments[idx++] = lineSegment( + prev, + pointFrom( + input[input.length - 1][0], + input[input.length - 1][1], + ), + ); + segments[idx++] = lineSegment( + pointFrom( + input[input.length - 1][0], + input[input.length - 1][1], + ), + offset( + input[input.length - 2][0], + input[input.length - 2][1], + input[input.length - 2][2] ?? 5, + "left", + pointFrom( + input[input.length - 1][0], + input[input.length - 1][1], + ), + ), + ); + + for (let i = input.length - 2; i > 0; i--) { + const a = segments[idx - 1][1]; + const b = offset( + input[i + 1][0], + input[i + 1][1], + input[i + 1][2] ?? 5, + "right", + pointFrom(input[i][0], input[i][1]), + ); + const c = offset( + input[i - 1][0], + input[i - 1][1], + input[i - 1][2] ?? 5, + "left", + pointFrom(input[i][0], input[i][1]), + ); + + segments[idx++] = lineSegment(a, b); // Main segment + segments[idx++] = lineSegment(b, c); // Bridge segment + } + + const last = segments[idx - 1][1]; + segments[idx++] = lineSegment( + last, + offset( + input[1][0], + input[1][1], + input[1][2] ?? 5, + "right", + pointFrom(input[0][0], input[0][1]), + ), + ); + + // Closing cap + segments[idx++] = lineSegment( + segments[idx - 2][1], + pointFrom(input[0][0], input[0][1]), + ); + // debugDrawPoint( + // pointFrom(element.x + segments[0], input[0][1]), + // ); + segments[idx++] = lineSegment( + pointFrom(input[0][0], input[0][1]), + segments[0][0], + ); + return segments; } @@ -85,7 +167,92 @@ export function getStroke( options: any, element: ExcalidrawFreeDrawElement, ): LocalPoint[] { - const segments = generateSegments(input); + const segments = generateSegments(input, element); + + // for (let j = 0; j < segments.length; j++) { + // segments.forEach((s, i) => { + // if (j !== i && j + 1 !== i && j !== i + 1) { + // const intersection = lineSegmentIntersectionPoints(segments[j], s); + // if (intersection?.length) { + // console.log( + // "intersection", + // j, + // i, + // pointDistance(segments[j][0], segments[j][1]), + // pointDistance(s[0], s[1]), + // //lineSegmentIntersectionPoints(segments[j], s), + // ); + // } + // } + // }); + // } + + // for (let i = 0; i < segments.length; i++) { + // if (i < 2 || i > segments.length - 3) { + // continue; + // } + + // const intersection1 = lineSegmentIntersectionPoints( + // segments[i - 2], + // segments[i], + // ); + // // if (intersection1) { + // // segments[i][0] = intersection1; + // // } + + // const intersection2 = lineSegmentIntersectionPoints( + // segments[i + 2], + // segments[i], + // ); + // // if (intersection2) { + // // segments[i][1] = intersection2; + // // } + + // if (!!intersection1 !== !!intersection2) { + // console.log("??", intersection1, intersection2); + // } + // } + + const colors = [ + "#FF0000", + "#00FF00", + "#0000FF", + // "#FFFF00", + // "#00FFFF", + // "#FF00FF", + // "#C0C0C0", + // "#800000", + // "#808000", + // "#008000", + // "#800080", + // "#008080", + // "#000080", + ]; + segments.forEach((s, i) => { + debugDrawLine( + lineSegment( + pointFrom(element.x + s[0][0], element.y + s[0][1]), + pointFrom(element.x + s[1][0], element.y + s[1][1]), + ), + { color: colors[i % colors.length], permanent: true }, + ); + }); + input.forEach((p, i) => { + if (i === 0) { + return; + } + + debugDrawLine( + lineSegment( + pointFrom( + element.x + input[i - 1][0], + element.y + input[i - 1][1], + ), + pointFrom(element.x + p[0], element.y + p[1]), + ), + { color: "#000000", permanent: true }, + ); + }); return [segments[0][0], ...segments.map((s) => s[1])]; } diff --git a/packages/element/src/renderElement.ts b/packages/element/src/renderElement.ts index 5c8c89c23a..aa59a11f3a 100644 --- a/packages/element/src/renderElement.ts +++ b/packages/element/src/renderElement.ts @@ -1119,6 +1119,15 @@ export function getFreedrawOutlinePoints(element: ExcalidrawFreeDrawElement) { last: true, }; + // return getStroke( + // [ + // [0, 0], + // [30, -30], + // [60, -30], + // ], + // options, + // element, + // ); return getStroke(inputPoints, options, element) as [number, number][]; }