aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/bezierFit.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/bezierFit.ts')
-rw-r--r--src/client/util/bezierFit.ts140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index d6f3f2340..e2055887d 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -586,6 +586,146 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
return { finalCtrls, error };
}
+// alpha determines how far away the tangents are, or the "tightness" of the bezier
+export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) {
+ const firstEnd = coordinates.length ? [coordinates[0], coordinates[0]] : [];
+ const lastEnd = coordinates.length ? [coordinates.lastElement(), coordinates.lastElement()] : [];
+ const points: Point[] = coordinates.slice(1, coordinates.length - 1).flatMap((pt, index, inkData) => {
+ const prevPt: Point = index === 0 ? firstEnd[0] : inkData[index - 1];
+ const nextPt: Point = index === inkData.length - 1 ? lastEnd[0] : inkData[index + 1];
+ if (prevPt.X === nextPt.X) {
+ const verticalDist = nextPt.Y - prevPt.Y;
+ return [{ X: pt.X, Y: pt.Y - alpha * verticalDist }, pt, pt, { X: pt.X, Y: pt.Y + alpha * verticalDist }];
+ } else if (prevPt.Y === nextPt.Y) {
+ const horizDist = nextPt.X - prevPt.X;
+ return [{ X: pt.X - alpha * horizDist, Y: pt.Y }, pt, pt, { X: pt.X + alpha * horizDist, Y: pt.Y }];
+ }
+ // tangent vectors between the adjacent points
+ const tanX = nextPt.X - prevPt.X;
+ const tanY = nextPt.Y - prevPt.Y;
+ const ctrlPt1: Point = { X: pt.X - alpha * tanX, Y: pt.Y - alpha * tanY };
+ const ctrlPt2: Point = { X: pt.X + alpha * tanX, Y: pt.Y + alpha * tanY };
+ return [ctrlPt1, pt, pt, ctrlPt2];
+ });
+ return [...firstEnd, ...points, ...lastEnd];
+}
+
+export function SVGToBezier(name: SVGType, attributes: any): Point[] {
+ switch (name) {
+ case 'line':
+ const x1 = parseInt(attributes.x1);
+ const x2 = parseInt(attributes.x2);
+ const y1 = parseInt(attributes.y1);
+ const y2 = parseInt(attributes.y2);
+ return [
+ { X: x1, Y: y1 },
+ { X: x1, Y: y1 },
+ { X: x2, Y: y2 },
+ { X: x2, Y: y2 },
+ ];
+ case 'circle':
+ case 'ellipse':
+ const c = 0.551915024494;
+ const centerX = parseInt(attributes.cx);
+ const centerY = parseInt(attributes.cy);
+ const radiusX = parseInt(attributes.rx) || parseInt(attributes.r);
+ const radiusY = parseInt(attributes.ry) || parseInt(attributes.r);
+ return [
+ { X: centerX, Y: centerY + radiusY },
+ { X: centerX + c * radiusX, Y: centerY + radiusY },
+ { X: centerX + radiusX, Y: centerY + c * radiusY },
+ { X: centerX + radiusX, Y: centerY },
+ { X: centerX + radiusX, Y: centerY },
+ { X: centerX + radiusX, Y: centerY - c * radiusY },
+ { X: centerX + c * radiusX, Y: centerY - radiusY },
+ { X: centerX, Y: centerY - radiusY },
+ { X: centerX, Y: centerY - radiusY },
+ { X: centerX - c * radiusX, Y: centerY - radiusY },
+ { X: centerX - radiusX, Y: centerY - c * radiusY },
+ { X: centerX - radiusX, Y: centerY },
+ { X: centerX - radiusX, Y: centerY },
+ { X: centerX - radiusX, Y: centerY + c * radiusY },
+ { X: centerX - c * radiusX, Y: centerY + radiusY },
+ { X: centerX, Y: centerY + radiusY },
+ ];
+ case 'rect':
+ const x = parseInt(attributes.x);
+ const y = parseInt(attributes.y);
+ const width = parseInt(attributes.width);
+ const height = parseInt(attributes.height);
+ return [
+ { X: x, Y: y },
+ { X: x, Y: y },
+ { X: x + width, Y: y },
+ { X: x + width, Y: y },
+ { X: x + width, Y: y },
+ { X: x + width, Y: y },
+ { X: x + width, Y: y + height },
+ { X: x + width, Y: y + height },
+ { X: x + width, Y: y + height },
+ { X: x + width, Y: y + height },
+ { X: x, Y: y + height },
+ { X: x, Y: y + height },
+ { X: x, Y: y + height },
+ { X: x, Y: y + height },
+ { X: x, Y: y },
+ { X: x, Y: y },
+ ];
+ case 'path':
+ const coordList: Point[] = [];
+ const startPt = attributes.d.match(/M(-?\d+\.?\d*),(-?\d+\.?\d*)/);
+ coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) });
+ const matches: RegExpMatchArray[] = Array.from(
+ attributes.d.matchAll(/Q(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|C(-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*) (-?\d+\.?\d*),(-?\d+\.?\d*)|L(-?\d+\.?\d*),(-?\d+\.?\d*)/g)
+ );
+ let lastPt: Point;
+ matches.forEach(match => {
+ if (match[0].startsWith('Q')) {
+ coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
+ coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
+ coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
+ coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
+ lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) };
+ } else if (match[0].startsWith('C')) {
+ coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) });
+ coordList.push({ X: parseInt(match[7]), Y: parseInt(match[8]) });
+ coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
+ coordList.push({ X: parseInt(match[9]), Y: parseInt(match[10]) });
+ lastPt = { X: parseInt(match[9]), Y: parseInt(match[10]) };
+ } else {
+ coordList.push(lastPt || { X: parseInt(startPt[1]), Y: parseInt(startPt[2]) });
+ coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
+ coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
+ coordList.push({ X: parseInt(match[11]), Y: parseInt(match[12]) });
+ lastPt = { X: parseInt(match[11]), Y: parseInt(match[12]) };
+ }
+ });
+ const hasZ = attributes.d.match(/Z/);
+ if (hasZ) {
+ coordList.push(lastPt);
+ coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) });
+ coordList.push({ X: parseInt(startPt[1]), Y: parseInt(startPt[2]) });
+ } else {
+ coordList.pop();
+ }
+ return coordList;
+ case 'polygon':
+ const coords: RegExpMatchArray[] = Array.from(attributes.points.matchAll(/(-?\d+\.?\d*),(-?\d+\.?\d*)/g));
+ let list: Point[] = [];
+ coords.forEach(coord => {
+ list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ list.push({ X: parseInt(coord[1]), Y: parseInt(coord[2]) });
+ });
+ const firstPts = list.splice(0, 2);
+ list = list.concat(firstPts);
+ return list;
+ default:
+ return [];
+ }
+}
+
/*
static double GetTValueFromSValue (const BezierRep &parent, double t, double endT, bool left, double influenceDistance, double &excess) {
double dist = 0;