diff options
Diffstat (limited to 'src/client/util/bezierFit.ts')
-rw-r--r-- | src/client/util/bezierFit.ts | 140 |
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; |