fix: Midpoint controls

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2026-03-23 17:58:26 +00:00
parent 8059518d85
commit 03d46aa62f
+59 -26
View File
@@ -80,8 +80,11 @@ import type {
import type { Drawable, Options } from "roughjs/bin/core"; import type { Drawable, Options } from "roughjs/bin/core";
import type { Point as RoughPoint } from "roughjs/bin/geometry"; import type { Point as RoughPoint } from "roughjs/bin/geometry";
const SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE = 0.3; const SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE = 0.25;
const SIMPLE_ROUNDED_ARROW_CP_LENGTH_RATIO = 1 / 4; // Lerp weight for interior-point tangents: 1 = pure bisector (smooth, no twist possible),
// 0 = chord-aligned (flat). Values between dampen the angle at midpoints without flipping.
const SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE = 0.9;
const SIMPLE_ROUNDED_ARROW_CP_LENGTH_RATIO = 1 / 3;
export class ShapeCache { export class ShapeCache {
private static rg = new RoughGenerator(); private static rg = new RoughGenerator();
@@ -683,8 +686,16 @@ export const generateLinearCollisionShape = (
bisUx = -inUy; bisUx = -inUy;
bisUy = inUx; bisUy = inUx;
} }
ptxn[i] = bisUx; const dotMid = bisUx * outUx + bisUy * outUy;
ptyn[i] = bisUy; const mx =
(1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUx +
SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUx;
const my =
(1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUy +
SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUy;
const mLen = Math.hypot(mx, my);
ptxn[i] = mx / mLen;
ptyn[i] = my / mLen;
} }
// Endpoints: reflect the adjacent interior tangent across the // Endpoints: reflect the adjacent interior tangent across the
@@ -1129,11 +1140,16 @@ const debugRoundedArrowControlPoints = (
bisUy = inUx; bisUy = inUx;
} }
const bx = bisUx; const dotMid = bisUx * outUx + bisUy * outUy;
const by = bisUy; const mx =
const bLen = Math.hypot(bx, by); (1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUx +
txn[i] = bx / bLen; SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUx;
tyn[i] = by / bLen; const my =
(1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUy +
SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUy;
const mLen = Math.hypot(mx, my);
txn[i] = mx / mLen;
tyn[i] = my / mLen;
} }
// Endpoints: reflect the adjacent interior tangent across the endpoint's own chord. // Endpoints: reflect the adjacent interior tangent across the endpoint's own chord.
@@ -1144,9 +1160,12 @@ const debugRoundedArrowControlPoints = (
const cux = cx / cLen; const cux = cx / cLen;
const cuy = cy / cLen; const cuy = cy / cLen;
const dot = txn[1] * cux + tyn[1] * cuy; const dot = txn[1] * cux + tyn[1] * cuy;
const eas = SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE; const rx =
const rx = (1 + eas) * dot * cux - eas * txn[1]; (1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cux -
const ry = (1 + eas) * dot * cuy - eas * tyn[1]; SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * txn[1];
const ry =
(1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cuy -
SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * tyn[1];
const rLen = Math.hypot(rx, ry); const rLen = Math.hypot(rx, ry);
txn[0] = rx / rLen; txn[0] = rx / rLen;
tyn[0] = ry / rLen; tyn[0] = ry / rLen;
@@ -1158,9 +1177,12 @@ const debugRoundedArrowControlPoints = (
const cux = cx / cLen; const cux = cx / cLen;
const cuy = cy / cLen; const cuy = cy / cLen;
const dot = txn[n - 2] * cux + tyn[n - 2] * cuy; const dot = txn[n - 2] * cux + tyn[n - 2] * cuy;
const eas = SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE; const rx =
const rx = (1 + eas) * dot * cux - eas * txn[n - 2]; (1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cux -
const ry = (1 + eas) * dot * cuy - eas * tyn[n - 2]; SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * txn[n - 2];
const ry =
(1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cuy -
SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * tyn[n - 2];
const rLen = Math.hypot(rx, ry); const rLen = Math.hypot(rx, ry);
txn[n - 1] = rx / rLen; txn[n - 1] = rx / rLen;
tyn[n - 1] = ry / rLen; tyn[n - 1] = ry / rLen;
@@ -1263,11 +1285,16 @@ const generateRoundedSimpleArrowShape = (
bisUy = inUx; bisUy = inUx;
} }
const bx = bisUx; const dotMid = bisUx * outUx + bisUy * outUy;
const by = bisUy; const mx =
const bLen = Math.hypot(bx, by); (1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUx +
txn[i] = bx / bLen; SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUx;
tyn[i] = by / bLen; const my =
(1 - SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE) * dotMid * outUy +
SIMPLE_ROUNDED_ARROW_MIDPOINT_ANGLE_SCALE * bisUy;
const mLen = Math.hypot(mx, my);
txn[i] = mx / mLen;
tyn[i] = my / mLen;
} }
// Endpoints: reflect the adjacent interior tangent across the endpoint's own chord. // Endpoints: reflect the adjacent interior tangent across the endpoint's own chord.
@@ -1280,9 +1307,12 @@ const generateRoundedSimpleArrowShape = (
const cux = cx / cLen; const cux = cx / cLen;
const cuy = cy / cLen; const cuy = cy / cLen;
const dot = txn[1] * cux + tyn[1] * cuy; const dot = txn[1] * cux + tyn[1] * cuy;
const eas = SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE; const rx =
const rx = (1 + eas) * dot * cux - eas * txn[1]; (1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cux -
const ry = (1 + eas) * dot * cuy - eas * tyn[1]; SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * txn[1];
const ry =
(1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cuy -
SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * tyn[1];
const rLen = Math.hypot(rx, ry); const rLen = Math.hypot(rx, ry);
txn[0] = rx / rLen; txn[0] = rx / rLen;
tyn[0] = ry / rLen; tyn[0] = ry / rLen;
@@ -1294,9 +1324,12 @@ const generateRoundedSimpleArrowShape = (
const cux = cx / cLen; const cux = cx / cLen;
const cuy = cy / cLen; const cuy = cy / cLen;
const dot = txn[n - 2] * cux + tyn[n - 2] * cuy; const dot = txn[n - 2] * cux + tyn[n - 2] * cuy;
const eas = SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE; const rx =
const rx = (1 + eas) * dot * cux - eas * txn[n - 2]; (1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cux -
const ry = (1 + eas) * dot * cuy - eas * tyn[n - 2]; SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * txn[n - 2];
const ry =
(1 + SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE) * dot * cuy -
SIMPLE_ROUNDED_ARROW_ENDPOINT_ANGLE_SCALE * tyn[n - 2];
const rLen = Math.hypot(rx, ry); const rLen = Math.hypot(rx, ry);
txn[n - 1] = rx / rLen; txn[n - 1] = rx / rLen;
tyn[n - 1] = ry / rLen; tyn[n - 1] = ry / rLen;