@@ -893,9 +893,8 @@ export const getArrowheadPoints = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (arrowhead === "diamond" || arrowhead === "diamond_outline") {
|
if (arrowhead === "diamond" || arrowhead === "diamond_outline") {
|
||||||
// point opposite to the arrowhead point — use the same Bezier tangent
|
// point opposite to the arrowhead point, just mirrored across the (tx, ty)
|
||||||
// direction (nx, ny) as the wings so the diamond isn't distorted on
|
// point
|
||||||
// rounded arrows where the tangent differs from the chord direction.
|
|
||||||
const ox = tx - nx * minSize * 2;
|
const ox = tx - nx * minSize * 2;
|
||||||
const oy = ty - ny * minSize * 2;
|
const oy = ty - ny * minSize * 2;
|
||||||
|
|
||||||
|
|||||||
@@ -790,9 +790,6 @@ export class LinearElementEditor {
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
// For curved (non-elbow) arrows the quadratic spline produces N-2 arcs
|
|
||||||
// for N anchor points. Cap the loop to the actual segment count so we
|
|
||||||
// don't request a midpoint for a segment that doesn't exist.
|
|
||||||
const [lines, segCurves] = deconstructLinearOrFreeDrawElement(element);
|
const [lines, segCurves] = deconstructLinearOrFreeDrawElement(element);
|
||||||
const segmentCount = lines.length + segCurves.length;
|
const segmentCount = lines.length + segCurves.length;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import type {
|
|||||||
} from "@excalidraw/excalidraw/scene/types";
|
} from "@excalidraw/excalidraw/scene/types";
|
||||||
|
|
||||||
import { elementWithCanvasCache } from "./renderElement";
|
import { elementWithCanvasCache } from "./renderElement";
|
||||||
import { debugDrawLine, debugDrawPoint, debugDrawPolygon } from "./visualdebug";
|
import { debugDrawLine, debugDrawPolygon } from "./visualdebug";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
canBecomePolygon,
|
canBecomePolygon,
|
||||||
@@ -90,7 +90,7 @@ const CP_CHORD_POWER = 1;
|
|||||||
// This factor [0, 1] controls how far the tangent direction is pulled toward
|
// This factor [0, 1] controls how far the tangent direction is pulled toward
|
||||||
// the bisector (the chord-bisector normal) linearly with turn sharpness.
|
// the bisector (the chord-bisector normal) linearly with turn sharpness.
|
||||||
// 0 = pure C2 spline; 1 = tangent fully aligned with the bisector.
|
// 0 = pure C2 spline; 1 = tangent fully aligned with the bisector.
|
||||||
const CP_ANGLE_CORRECTION = 0.5;
|
const CP_ANGLE_CORRECTION = 1;
|
||||||
|
|
||||||
export class ShapeCache {
|
export class ShapeCache {
|
||||||
private static rg = new RoughGenerator();
|
private static rg = new RoughGenerator();
|
||||||
@@ -665,8 +665,8 @@ export const generateLinearCollisionShape = (
|
|||||||
data: rotateLocal(points[1][0], points[1][1]),
|
data: rotateLocal(points[1][0], points[1][1]),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Chord-length C2 spline – mirrors generateRoundedSimpleArrowShape exactly
|
// Chord-length C2 spline. Mirrors generateRoundedSimpleArrowShape
|
||||||
// so hit-testing matches rendering.
|
// exactly so hit-testing matches rendering.
|
||||||
const n = points.length - 1;
|
const n = points.length - 1;
|
||||||
const h = new Float64Array(n);
|
const h = new Float64Array(n);
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
@@ -725,6 +725,40 @@ export const generateLinearCollisionShape = (
|
|||||||
mlen[i] = Math.max(1e-10, Math.hypot(mx[i], my[i]));
|
mlen[i] = Math.max(1e-10, Math.hypot(mx[i], my[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At interior knots, blend the C2 tangent direction toward the
|
||||||
|
// bisector direction by a factor proportional to turn sharpness *
|
||||||
|
// CP_ANGLE_CORRECTION
|
||||||
|
for (let k = 1; k < n; k++) {
|
||||||
|
const d1x = (points[k][0] - points[k - 1][0]) / h[k - 1];
|
||||||
|
const d1y = (points[k][1] - points[k - 1][1]) / h[k - 1];
|
||||||
|
const d2x = (points[k + 1][0] - points[k][0]) / h[k];
|
||||||
|
const d2y = (points[k + 1][1] - points[k][1]) / h[k];
|
||||||
|
const dot = d1x * d2x + d1y * d2y;
|
||||||
|
const t = ((1 - dot) / 2) * CP_ANGLE_CORRECTION;
|
||||||
|
if (t < 1e-6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bx = d1x + d2x;
|
||||||
|
const by = d1y + d2y;
|
||||||
|
const blen = Math.hypot(bx, by);
|
||||||
|
if (blen < 1e-10) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let px = bx / blen;
|
||||||
|
let py = by / blen;
|
||||||
|
const tx = mx[k] / mlen[k];
|
||||||
|
const ty = my[k] / mlen[k];
|
||||||
|
if (tx * px + ty * py < 0) {
|
||||||
|
px = -px;
|
||||||
|
py = -py;
|
||||||
|
}
|
||||||
|
const blendX = tx + t * (px - tx);
|
||||||
|
const blendY = ty + t * (py - ty);
|
||||||
|
const blendLen = Math.max(1e-10, Math.hypot(blendX, blendY));
|
||||||
|
mx[k] = (blendX / blendLen) * mlen[k];
|
||||||
|
my[k] = (blendY / blendLen) * mlen[k];
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const cpDist = Math.pow(h[i], CP_CHORD_POWER) / 3;
|
const cpDist = Math.pow(h[i], CP_CHORD_POWER) / 3;
|
||||||
const cp1x = points[i][0] + (mx[i] / mlen[i]) * cpDist;
|
const cp1x = points[i][0] + (mx[i] / mlen[i]) * cpDist;
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ describe("getElementBounds", () => {
|
|||||||
} as ExcalidrawLinearElement;
|
} as ExcalidrawLinearElement;
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
||||||
expect(x1).toEqual(364.8060248040008);
|
expect(x1).toEqual(366.0476290709661);
|
||||||
expect(y1).toEqual(186.47330186064582);
|
expect(y1).toEqual(186.59818534770224);
|
||||||
expect(x2).toEqual(494.2327553105778);
|
expect(x2).toEqual(494.6034220048372);
|
||||||
expect(y2).toEqual(322.3237366886165);
|
expect(y2).toEqual(324.16489799221546);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -434,12 +434,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(midPointsWithRoundEdge).toMatchInlineSnapshot(`
|
expect(midPointsWithRoundEdge).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"42.76190",
|
"51.36383",
|
||||||
"62.13334",
|
"54.86323",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"87.23810",
|
"81.64884",
|
||||||
"37.65714",
|
"43.04575",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -482,7 +482,7 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`12`,
|
`12`,
|
||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`6`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`7`);
|
||||||
|
|
||||||
expect([line.x, line.y]).toEqual([
|
expect([line.x, line.y]).toEqual([
|
||||||
points[0][0] + deltaX,
|
points[0][0] + deltaX,
|
||||||
@@ -499,12 +499,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"97.30521",
|
"101.36383",
|
||||||
"77.27340",
|
"74.86323",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"133.70877",
|
"131.64884",
|
||||||
"60.46424",
|
"63.04575",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -809,12 +809,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"5.35122",
|
"22.32088",
|
||||||
"49.57854",
|
"37.43003",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"87.32439",
|
"81.55727",
|
||||||
"37.60536",
|
"43.21091",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -898,12 +898,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
"42.76190",
|
"51.36383",
|
||||||
"62.13334",
|
"54.86323",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"87.23810",
|
"81.64884",
|
||||||
"37.65714",
|
"43.04575",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -1065,8 +1065,8 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": "86.53100",
|
||||||
"y": 60,
|
"y": "72.83556",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -1181,12 +1181,12 @@ describe("Test Linear Elements", () => {
|
|||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"18.54397",
|
20,
|
||||||
"19.35821",
|
20,
|
||||||
105,
|
105,
|
||||||
80,
|
80,
|
||||||
"56.33510",
|
"56.68277",
|
||||||
"47.11973",
|
"47.27188",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -1196,7 +1196,7 @@ describe("Test Linear Elements", () => {
|
|||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"height": 130,
|
"height": 130,
|
||||||
"width": "369.28398",
|
"width": "368.53316",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -1208,7 +1208,7 @@ describe("Test Linear Elements", () => {
|
|||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": "274.28398",
|
"x": "273.53316",
|
||||||
"y": 45,
|
"y": 45,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@@ -1223,12 +1223,12 @@ describe("Test Linear Elements", () => {
|
|||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"19.96519",
|
20,
|
||||||
"9.07108",
|
35,
|
||||||
"504.28398",
|
"503.53316",
|
||||||
"146.41791",
|
"119.02540",
|
||||||
"204.79250",
|
"204.47758",
|
||||||
"77.74450",
|
"77.01270",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -205,7 +205,6 @@ export const getCurvePathOps = (shape: Drawable): Op[] => {
|
|||||||
return set.ops;
|
return set.ops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return shape.sets[0].ops;
|
return shape.sets[0].ops;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,7 +325,7 @@ export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
for (const operation of ops) {
|
for (const operation of ops) {
|
||||||
if (operation.op === "move") {
|
if (operation.op === "move") {
|
||||||
if (fillPathSet) {
|
if (fillPathSet) {
|
||||||
// fillPath is always a single run — no odd/even skipping needed
|
// fillPath is always a single run, no odd/even skipping needed
|
||||||
points.push(pointFrom(operation.data[0], operation.data[1]));
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
} else {
|
} else {
|
||||||
odd = !odd;
|
odd = !odd;
|
||||||
|
|||||||
Reference in New Issue
Block a user