Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
164 lines
4.0 KiB
TypeScript
164 lines
4.0 KiB
TypeScript
import type { Point } from '../rough/geometry.js';
|
||
|
||
// distance between 2 points
|
||
function distance(p1: Point, p2: Point): number {
|
||
return Math.sqrt(distanceSq(p1, p2));
|
||
}
|
||
|
||
// distance between 2 points squared
|
||
function distanceSq(p1: Point, p2: Point): number {
|
||
return Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2);
|
||
}
|
||
|
||
// Sistance squared from a point p to the line segment vw
|
||
function distanceToSegmentSq(p: Point, v: Point, w: Point): number {
|
||
const l2 = distanceSq(v, w);
|
||
if (l2 === 0) {
|
||
return distanceSq(p, v);
|
||
}
|
||
let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
|
||
t = Math.max(0, Math.min(1, t));
|
||
return distanceSq(p, lerp(v, w, t));
|
||
}
|
||
|
||
function lerp(a: Point, b: Point, t: number): Point {
|
||
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
||
}
|
||
|
||
// Adapted from https://seant23.wordpress.com/2010/11/12/offset-bezier-curves/
|
||
function flatness(points: readonly Point[], offset: number): number {
|
||
const p1 = points[offset + 0];
|
||
const p2 = points[offset + 1];
|
||
const p3 = points[offset + 2];
|
||
const p4 = points[offset + 3];
|
||
|
||
let ux = 3 * p2[0] - 2 * p1[0] - p4[0];
|
||
ux *= ux;
|
||
let uy = 3 * p2[1] - 2 * p1[1] - p4[1];
|
||
uy *= uy;
|
||
let vx = 3 * p3[0] - 2 * p4[0] - p1[0];
|
||
vx *= vx;
|
||
let vy = 3 * p3[1] - 2 * p4[1] - p1[1];
|
||
vy *= vy;
|
||
|
||
if (ux < vx) {
|
||
ux = vx;
|
||
}
|
||
|
||
if (uy < vy) {
|
||
uy = vy;
|
||
}
|
||
|
||
return ux + uy;
|
||
}
|
||
|
||
function getPointsOnBezierCurveWithSplitting(
|
||
points: readonly Point[],
|
||
offset: number,
|
||
tolerance: number,
|
||
newPoints?: Point[]
|
||
): Point[] {
|
||
const outPoints = newPoints || [];
|
||
if (flatness(points, offset) < tolerance) {
|
||
const p0 = points[offset + 0];
|
||
if (outPoints.length) {
|
||
const d = distance(outPoints[outPoints.length - 1], p0);
|
||
if (d > 1) {
|
||
outPoints.push(p0);
|
||
}
|
||
} else {
|
||
outPoints.push(p0);
|
||
}
|
||
outPoints.push(points[offset + 3]);
|
||
} else {
|
||
// subdivide
|
||
const t = 0.5;
|
||
const p1 = points[offset + 0];
|
||
const p2 = points[offset + 1];
|
||
const p3 = points[offset + 2];
|
||
const p4 = points[offset + 3];
|
||
|
||
const q1 = lerp(p1, p2, t);
|
||
const q2 = lerp(p2, p3, t);
|
||
const q3 = lerp(p3, p4, t);
|
||
|
||
const r1 = lerp(q1, q2, t);
|
||
const r2 = lerp(q2, q3, t);
|
||
|
||
const red = lerp(r1, r2, t);
|
||
|
||
getPointsOnBezierCurveWithSplitting(
|
||
[p1, q1, r1, red],
|
||
0,
|
||
tolerance,
|
||
outPoints
|
||
);
|
||
getPointsOnBezierCurveWithSplitting(
|
||
[red, r2, q3, p4],
|
||
0,
|
||
tolerance,
|
||
outPoints
|
||
);
|
||
}
|
||
return outPoints;
|
||
}
|
||
|
||
export function simplify(points: readonly Point[], distance: number): Point[] {
|
||
return simplifyPoints(points, 0, points.length, distance);
|
||
}
|
||
|
||
// Ramer–Douglas–Peucker algorithm
|
||
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
|
||
function simplifyPoints(
|
||
points: readonly Point[],
|
||
start: number,
|
||
end: number,
|
||
epsilon: number,
|
||
newPoints?: Point[]
|
||
): Point[] {
|
||
const outPoints = newPoints || [];
|
||
|
||
// find the most distance point from the endpoints
|
||
const s = points[start];
|
||
const e = points[end - 1];
|
||
let maxDistSq = 0;
|
||
let maxNdx = 1;
|
||
for (let i = start + 1; i < end - 1; ++i) {
|
||
const distSq = distanceToSegmentSq(points[i], s, e);
|
||
if (distSq > maxDistSq) {
|
||
maxDistSq = distSq;
|
||
maxNdx = i;
|
||
}
|
||
}
|
||
|
||
// if that point is too far, split
|
||
if (Math.sqrt(maxDistSq) > epsilon) {
|
||
simplifyPoints(points, start, maxNdx + 1, epsilon, outPoints);
|
||
simplifyPoints(points, maxNdx, end, epsilon, outPoints);
|
||
} else {
|
||
if (!outPoints.length) {
|
||
outPoints.push(s);
|
||
}
|
||
outPoints.push(e);
|
||
}
|
||
|
||
return outPoints;
|
||
}
|
||
|
||
export function pointsOnBezierCurves(
|
||
points: Point[],
|
||
tolerance = 0.15,
|
||
distance?: number
|
||
): Point[] {
|
||
const newPoints: Point[] = [];
|
||
const numSegments = (points.length - 1) / 3;
|
||
for (let i = 0; i < numSegments; i++) {
|
||
const offset = i * 3;
|
||
getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints);
|
||
}
|
||
if (distance && distance > 0) {
|
||
return simplifyPoints(newPoints, 0, newPoints.length, distance);
|
||
}
|
||
return newPoints;
|
||
}
|