@canvas-commons/2d 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/editor/index.css +40 -0
- package/editor/index.js +525 -0
- package/editor/index.js.map +1 -0
- package/lib/index-OvpZ-GsA.d.ts +5673 -0
- package/lib/index-OvpZ-GsA.d.ts.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +10326 -0
- package/lib/index.js.map +1 -0
- package/lib/jsx-dev-runtime.d.ts +2 -0
- package/lib/jsx-dev-runtime.js +2 -0
- package/lib/jsx-runtime.d.ts +2 -0
- package/lib/jsx-runtime.js +28 -0
- package/lib/jsx-runtime.js.map +1 -0
- package/package.json +79 -0
- package/src/editor/NodeInspectorConfig.tsx +76 -0
- package/src/editor/PreviewOverlayConfig.tsx +65 -0
- package/src/editor/Provider.tsx +109 -0
- package/src/editor/SceneGraphTabConfig.tsx +87 -0
- package/src/editor/icons/CircleIcon.tsx +7 -0
- package/src/editor/icons/CodeIcon.tsx +8 -0
- package/src/editor/icons/CurveIcon.tsx +7 -0
- package/src/editor/icons/GridIcon.tsx +7 -0
- package/src/editor/icons/IconMap.ts +35 -0
- package/src/editor/icons/ImgIcon.tsx +8 -0
- package/src/editor/icons/LayoutIcon.tsx +9 -0
- package/src/editor/icons/LineIcon.tsx +7 -0
- package/src/editor/icons/NodeIcon.tsx +7 -0
- package/src/editor/icons/RayIcon.tsx +7 -0
- package/src/editor/icons/RectIcon.tsx +7 -0
- package/src/editor/icons/ShapeIcon.tsx +7 -0
- package/src/editor/icons/TxtIcon.tsx +8 -0
- package/src/editor/icons/VideoIcon.tsx +7 -0
- package/src/editor/icons/View2DIcon.tsx +10 -0
- package/src/editor/index.css +0 -0
- package/src/editor/index.ts +19 -0
- package/src/editor/shortcuts.ts +27 -0
- package/src/editor/tree/DetachedRoot.tsx +27 -0
- package/src/editor/tree/NodeElement.tsx +72 -0
- package/src/editor/tree/TreeElement.tsx +70 -0
- package/src/editor/tree/TreeRoot.tsx +10 -0
- package/src/editor/tree/ViewRoot.tsx +20 -0
- package/src/editor/tree/index.module.scss +45 -0
- package/src/editor/tree/index.ts +4 -0
- package/src/editor/tree/navigation.ts +145 -0
- package/src/editor/tsconfig.build.json +5 -0
- package/src/editor/tsconfig.json +12 -0
- package/src/editor/tsdoc.json +4 -0
- package/src/editor/utils/SignalSet.ts +37 -0
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/vite-env.d.ts +1 -0
- package/src/lib/code/CodeCursor.ts +468 -0
- package/src/lib/code/CodeDiffer.ts +77 -0
- package/src/lib/code/CodeFragment.ts +96 -0
- package/src/lib/code/CodeHighlighter.ts +73 -0
- package/src/lib/code/CodeMetrics.ts +47 -0
- package/src/lib/code/CodeRange.test.ts +113 -0
- package/src/lib/code/CodeRange.ts +222 -0
- package/src/lib/code/CodeScope.ts +100 -0
- package/src/lib/code/CodeSelection.ts +28 -0
- package/src/lib/code/CodeSignal.ts +348 -0
- package/src/lib/code/CodeTokenizer.ts +54 -0
- package/src/lib/code/DefaultHighlightStyle.ts +98 -0
- package/src/lib/code/LezerHighlighter.ts +113 -0
- package/src/lib/code/diff.test.ts +311 -0
- package/src/lib/code/diff.ts +319 -0
- package/src/lib/code/extractRange.ts +125 -0
- package/src/lib/code/index.ts +13 -0
- package/src/lib/components/Bezier.ts +103 -0
- package/src/lib/components/Camera.ts +401 -0
- package/src/lib/components/Circle.ts +310 -0
- package/src/lib/components/Code.ts +532 -0
- package/src/lib/components/CubicBezier.ts +115 -0
- package/src/lib/components/Curve.ts +460 -0
- package/src/lib/components/Grid.ts +134 -0
- package/src/lib/components/Icon.ts +153 -0
- package/src/lib/components/Img.ts +328 -0
- package/src/lib/components/Knot.ts +156 -0
- package/src/lib/components/Latex.ts +537 -0
- package/src/lib/components/Layout.ts +1101 -0
- package/src/lib/components/Line.ts +394 -0
- package/src/lib/components/Node.ts +1941 -0
- package/src/lib/components/Path.ts +132 -0
- package/src/lib/components/Polygon.ts +266 -0
- package/src/lib/components/QuadBezier.ts +103 -0
- package/src/lib/components/Ray.ts +126 -0
- package/src/lib/components/Rect.ts +219 -0
- package/src/lib/components/SVG.ts +853 -0
- package/src/lib/components/Shape.ts +322 -0
- package/src/lib/components/Spline.ts +318 -0
- package/src/lib/components/Txt.test.tsx +81 -0
- package/src/lib/components/Txt.ts +204 -0
- package/src/lib/components/TxtLeaf.ts +210 -0
- package/src/lib/components/Video.ts +368 -0
- package/src/lib/components/View2D.ts +85 -0
- package/src/lib/components/__logs__/image-without-source.ts +18 -0
- package/src/lib/components/__logs__/line-without-points.ts +31 -0
- package/src/lib/components/__logs__/reactive-playback-rate.ts +22 -0
- package/src/lib/components/__logs__/spline-with-insufficient-knots.ts +25 -0
- package/src/lib/components/__tests__/Camera.test.tsx +73 -0
- package/src/lib/components/__tests__/children.test.tsx +142 -0
- package/src/lib/components/__tests__/clone.test.tsx +126 -0
- package/src/lib/components/__tests__/fontInheritance.test.tsx +102 -0
- package/src/lib/components/__tests__/generatorTest.ts +27 -0
- package/src/lib/components/__tests__/mockScene2D.ts +50 -0
- package/src/lib/components/__tests__/query.test.tsx +122 -0
- package/src/lib/components/__tests__/state.test.tsx +60 -0
- package/src/lib/components/index.ts +26 -0
- package/src/lib/components/types.ts +35 -0
- package/src/lib/curves/ArcSegment.ts +169 -0
- package/src/lib/curves/CircleSegment.ts +99 -0
- package/src/lib/curves/CubicBezierSegment.ts +80 -0
- package/src/lib/curves/CurveDrawingInfo.ts +11 -0
- package/src/lib/curves/CurvePoint.ts +15 -0
- package/src/lib/curves/CurveProfile.ts +28 -0
- package/src/lib/curves/KnotInfo.ts +10 -0
- package/src/lib/curves/LineSegment.ts +75 -0
- package/src/lib/curves/Polynomial.ts +355 -0
- package/src/lib/curves/Polynomial2D.ts +62 -0
- package/src/lib/curves/PolynomialSegment.ts +151 -0
- package/src/lib/curves/QuadBezierSegment.ts +66 -0
- package/src/lib/curves/Segment.ts +31 -0
- package/src/lib/curves/UniformPolynomialCurveSampler.ts +93 -0
- package/src/lib/curves/createCurveProfileLerp.ts +471 -0
- package/src/lib/curves/getBezierSplineProfile.ts +227 -0
- package/src/lib/curves/getCircleProfile.ts +86 -0
- package/src/lib/curves/getPathProfile.ts +177 -0
- package/src/lib/curves/getPointAtDistance.ts +21 -0
- package/src/lib/curves/getPolylineProfile.test.ts +21 -0
- package/src/lib/curves/getPolylineProfile.ts +88 -0
- package/src/lib/curves/getRectProfile.ts +138 -0
- package/src/lib/curves/index.ts +16 -0
- package/src/lib/decorators/canvasStyleSignal.ts +15 -0
- package/src/lib/decorators/colorSignal.ts +9 -0
- package/src/lib/decorators/compound.ts +85 -0
- package/src/lib/decorators/computed.ts +18 -0
- package/src/lib/decorators/defaultStyle.ts +15 -0
- package/src/lib/decorators/filtersSignal.ts +133 -0
- package/src/lib/decorators/index.ts +10 -0
- package/src/lib/decorators/initializers.ts +32 -0
- package/src/lib/decorators/nodeName.ts +13 -0
- package/src/lib/decorators/signal.test.ts +89 -0
- package/src/lib/decorators/signal.ts +348 -0
- package/src/lib/decorators/spacingSignal.ts +15 -0
- package/src/lib/decorators/transformSignals.test.ts +858 -0
- package/src/lib/decorators/transformSignals.ts +1633 -0
- package/src/lib/decorators/vector2Signal.ts +35 -0
- package/src/lib/globals.d.ts +2 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/jsx-dev-runtime.ts +2 -0
- package/src/lib/jsx-runtime.ts +45 -0
- package/src/lib/morphers/PathMorpher.ts +8 -0
- package/src/lib/morphers/defaultMorpher.ts +43 -0
- package/src/lib/morphers/index.ts +4 -0
- package/src/lib/morphers/manimMorpher.ts +658 -0
- package/src/lib/parse-svg-path.d.ts +14 -0
- package/src/lib/partials/Filter.ts +185 -0
- package/src/lib/partials/Gradient.ts +103 -0
- package/src/lib/partials/Pattern.ts +35 -0
- package/src/lib/partials/RoughConfig.ts +180 -0
- package/src/lib/partials/ShaderConfig.ts +122 -0
- package/src/lib/partials/index.ts +5 -0
- package/src/lib/partials/types.ts +58 -0
- package/src/lib/scenes/Scene2D.ts +167 -0
- package/src/lib/scenes/index.ts +3 -0
- package/src/lib/scenes/makeScene2D.ts +19 -0
- package/src/lib/scenes/useScene2D.ts +6 -0
- package/src/lib/tsconfig.build.json +5 -0
- package/src/lib/tsconfig.json +11 -0
- package/src/lib/tsdoc.json +4 -0
- package/src/lib/utils/CanvasUtils.ts +304 -0
- package/src/lib/utils/PathDataBuilder.ts +320 -0
- package/src/lib/utils/diff.test.ts +453 -0
- package/src/lib/utils/diff.ts +148 -0
- package/src/lib/utils/index.ts +5 -0
- package/src/lib/utils/is.ts +11 -0
- package/src/lib/utils/makeSignalExtensions.ts +29 -0
- package/src/lib/utils/rough.ts +513 -0
- package/src/lib/utils/withDefaults.tsx +26 -0
- package/src/tsconfig.base.json +18 -0
- package/src/tsconfig.build.json +8 -0
- package/src/tsconfig.json +5 -0
- package/tsconfig.project.json +7 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {BBox, DEG2RAD, Matrix2D, Vector2, lazy} from '@canvas-commons/core';
|
|
2
|
+
import {View2D} from '../components/View2D';
|
|
3
|
+
import {CurvePoint} from './CurvePoint';
|
|
4
|
+
import {Segment} from './Segment';
|
|
5
|
+
|
|
6
|
+
export class ArcSegment extends Segment {
|
|
7
|
+
@lazy(() => {
|
|
8
|
+
const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
9
|
+
const el = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
10
|
+
root.appendChild(el);
|
|
11
|
+
View2D.shadowRoot.appendChild(root);
|
|
12
|
+
return el;
|
|
13
|
+
})
|
|
14
|
+
private static el: SVGPathElement;
|
|
15
|
+
public readonly center: Vector2;
|
|
16
|
+
// angle in radian
|
|
17
|
+
public readonly startAngle: number;
|
|
18
|
+
public readonly deltaAngle: number;
|
|
19
|
+
public readonly xAxisRotation: number;
|
|
20
|
+
private xAxisRotationMatrix: DOMMatrix;
|
|
21
|
+
public override readonly points: Vector2[];
|
|
22
|
+
private length: number;
|
|
23
|
+
|
|
24
|
+
public constructor(
|
|
25
|
+
public readonly startPoint: Vector2,
|
|
26
|
+
public readonly radius: Vector2,
|
|
27
|
+
public readonly xAxisRotationDegree: number,
|
|
28
|
+
public readonly largeArcFlag: number,
|
|
29
|
+
public readonly sweepFlag: number,
|
|
30
|
+
public readonly endPoint: Vector2,
|
|
31
|
+
) {
|
|
32
|
+
super();
|
|
33
|
+
|
|
34
|
+
this.xAxisRotation = this.xAxisRotationDegree * DEG2RAD;
|
|
35
|
+
this.radius = new Vector2(Math.abs(radius.x), Math.abs(radius.y));
|
|
36
|
+
|
|
37
|
+
const pAccent = startPoint
|
|
38
|
+
.sub(endPoint)
|
|
39
|
+
.div(2)
|
|
40
|
+
.transform(Matrix2D.fromRotation(-xAxisRotationDegree).domMatrix);
|
|
41
|
+
|
|
42
|
+
const L =
|
|
43
|
+
(pAccent.x * pAccent.x) / (radius.x * radius.x) +
|
|
44
|
+
(pAccent.y * pAccent.y) / (radius.y * radius.y);
|
|
45
|
+
|
|
46
|
+
if (L > 1) {
|
|
47
|
+
const Lsqrt = Math.sqrt(L);
|
|
48
|
+
radius.x = Lsqrt * radius.x;
|
|
49
|
+
radius.y = Lsqrt * radius.y;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cAccent = new Vector2(
|
|
53
|
+
radius.ctg * pAccent.y,
|
|
54
|
+
radius.perpendicular.ctg * pAccent.x,
|
|
55
|
+
).scale(
|
|
56
|
+
Math.sqrt(
|
|
57
|
+
1 /
|
|
58
|
+
((pAccent.x * pAccent.x) / (radius.x * radius.x) +
|
|
59
|
+
(pAccent.y * pAccent.y) / (radius.y * radius.y)) -
|
|
60
|
+
1,
|
|
61
|
+
) * (largeArcFlag === sweepFlag ? -1 : 1),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
this.xAxisRotationMatrix =
|
|
65
|
+
Matrix2D.fromRotation(xAxisRotationDegree).domMatrix;
|
|
66
|
+
this.center = cAccent
|
|
67
|
+
.transform(this.xAxisRotationMatrix)
|
|
68
|
+
.add(startPoint.add(endPoint).div(2));
|
|
69
|
+
|
|
70
|
+
const q = pAccent.sub(cAccent).div(radius);
|
|
71
|
+
const s = pAccent.scale(-1).sub(cAccent).div(radius);
|
|
72
|
+
this.startAngle = q.radians;
|
|
73
|
+
this.deltaAngle = Vector2.angleBetween(q, s) % (Math.PI * 2);
|
|
74
|
+
if (this.sweepFlag === 0 && this.deltaAngle > 0) {
|
|
75
|
+
this.deltaAngle -= Math.PI * 2;
|
|
76
|
+
}
|
|
77
|
+
if (this.sweepFlag === 1 && this.deltaAngle < 0) {
|
|
78
|
+
this.deltaAngle += Math.PI * 2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ArcSegment.el.setAttribute(
|
|
82
|
+
'd',
|
|
83
|
+
`M ${this.startPoint.x} ${this.startPoint.y} A ${this.radius.x} ${this.radius.y} ${this.xAxisRotationDegree} ${this.largeArcFlag} ${this.sweepFlag} ${this.endPoint.x} ${this.endPoint.y}`,
|
|
84
|
+
);
|
|
85
|
+
this.length = ArcSegment.el.getTotalLength();
|
|
86
|
+
|
|
87
|
+
const bbox = new BBox(ArcSegment.el.getBBox());
|
|
88
|
+
this.points = [bbox.topLeft, bbox.bottomRight];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public getAnglePosition(angle: number) {
|
|
92
|
+
return this.radius
|
|
93
|
+
.mul(Vector2.fromRadians(angle))
|
|
94
|
+
.transform(this.xAxisRotationMatrix)
|
|
95
|
+
.add(this.center);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public getAngleDerivative(angle: number) {
|
|
99
|
+
return new Vector2(
|
|
100
|
+
-this.radius.x * Math.sin(angle),
|
|
101
|
+
this.radius.y * Math.cos(angle),
|
|
102
|
+
).transform(this.xAxisRotationMatrix);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public draw(
|
|
106
|
+
context: CanvasRenderingContext2D | Path2D,
|
|
107
|
+
start: number,
|
|
108
|
+
end: number,
|
|
109
|
+
move: boolean,
|
|
110
|
+
): [CurvePoint, CurvePoint] {
|
|
111
|
+
const startAngle = this.startAngle + this.deltaAngle * start;
|
|
112
|
+
const endAngle = this.startAngle + this.deltaAngle * end;
|
|
113
|
+
const startPos = this.getPoint(start);
|
|
114
|
+
const endPos = this.getPoint(end);
|
|
115
|
+
|
|
116
|
+
if (move) context.moveTo(startPos.position.x, startPos.position.y);
|
|
117
|
+
|
|
118
|
+
context.ellipse(
|
|
119
|
+
this.center.x,
|
|
120
|
+
this.center.y,
|
|
121
|
+
this.radius.x,
|
|
122
|
+
this.radius.y,
|
|
123
|
+
this.xAxisRotation,
|
|
124
|
+
startAngle,
|
|
125
|
+
endAngle,
|
|
126
|
+
this.sweepFlag === 0,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return [startPos, endPos];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public getPoint(distance: number): CurvePoint {
|
|
133
|
+
const angle = this.startAngle + distance * this.deltaAngle;
|
|
134
|
+
const tangent = this.getAngleDerivative(angle).normalized;
|
|
135
|
+
return {
|
|
136
|
+
position:
|
|
137
|
+
distance === 0
|
|
138
|
+
? this.startPoint
|
|
139
|
+
: distance === 1
|
|
140
|
+
? this.endPoint
|
|
141
|
+
: this.getAnglePosition(angle),
|
|
142
|
+
tangent,
|
|
143
|
+
normal: tangent.perpendicular,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public get arcLength(): number {
|
|
148
|
+
return this.length;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public toSVGCommands(start = 0, end = 1, move = false): string {
|
|
152
|
+
const startPos = this.getPoint(start).position;
|
|
153
|
+
const endPos = this.getPoint(end).position;
|
|
154
|
+
|
|
155
|
+
const commands: string[] = [];
|
|
156
|
+
if (move) {
|
|
157
|
+
commands.push(`M ${startPos.x} ${startPos.y}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const angleCovered = (end - start) * Math.abs(this.deltaAngle);
|
|
161
|
+
const largeArc = angleCovered > Math.PI ? 1 : 0;
|
|
162
|
+
|
|
163
|
+
commands.push(
|
|
164
|
+
`A ${this.radius.x} ${this.radius.y} ${this.xAxisRotationDegree} ${largeArc} ${this.sweepFlag} ${endPos.x} ${endPos.y}`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return commands.join(' ');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {Vector2, clamp} from '@canvas-commons/core';
|
|
2
|
+
import {CurvePoint} from './CurvePoint';
|
|
3
|
+
import {Segment} from './Segment';
|
|
4
|
+
|
|
5
|
+
export class CircleSegment extends Segment {
|
|
6
|
+
private readonly length: number;
|
|
7
|
+
private readonly angle: number;
|
|
8
|
+
public override readonly points: Vector2[];
|
|
9
|
+
|
|
10
|
+
public constructor(
|
|
11
|
+
private center: Vector2,
|
|
12
|
+
private radius: number,
|
|
13
|
+
private from: Vector2,
|
|
14
|
+
private to: Vector2,
|
|
15
|
+
private counter: boolean,
|
|
16
|
+
) {
|
|
17
|
+
super();
|
|
18
|
+
this.angle = Math.acos(clamp(-1, 1, from.dot(to)));
|
|
19
|
+
this.length = Math.abs(this.angle * radius);
|
|
20
|
+
const edgeVector = new Vector2(1, 1).scale(radius);
|
|
21
|
+
this.points = [center.sub(edgeVector), center.add(edgeVector)];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public get arcLength(): number {
|
|
25
|
+
return this.length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public draw(
|
|
29
|
+
context: CanvasRenderingContext2D | Path2D,
|
|
30
|
+
from: number,
|
|
31
|
+
to: number,
|
|
32
|
+
): [CurvePoint, CurvePoint] {
|
|
33
|
+
const counterFactor = this.counter ? -1 : 1;
|
|
34
|
+
const startAngle = this.from.radians + from * this.angle * counterFactor;
|
|
35
|
+
const endAngle = this.to.radians - (1 - to) * this.angle * counterFactor;
|
|
36
|
+
|
|
37
|
+
if (Math.abs(this.angle) > 0.0001) {
|
|
38
|
+
context.arc(
|
|
39
|
+
this.center.x,
|
|
40
|
+
this.center.y,
|
|
41
|
+
this.radius,
|
|
42
|
+
startAngle,
|
|
43
|
+
endAngle,
|
|
44
|
+
this.counter,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const startNormal = Vector2.fromRadians(startAngle);
|
|
49
|
+
const endNormal = Vector2.fromRadians(endAngle);
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
position: this.center.add(startNormal.scale(this.radius)),
|
|
54
|
+
tangent: this.counter ? startNormal : startNormal.flipped,
|
|
55
|
+
normal: this.counter ? startNormal.flipped : startNormal,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
position: this.center.add(endNormal.scale(this.radius)),
|
|
59
|
+
tangent: this.counter ? endNormal.flipped : endNormal,
|
|
60
|
+
normal: this.counter ? endNormal.flipped : endNormal,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public getPoint(distance: number): CurvePoint {
|
|
66
|
+
const counterFactor = this.counter ? -1 : 1;
|
|
67
|
+
const angle = this.from.radians + distance * this.angle * counterFactor;
|
|
68
|
+
|
|
69
|
+
const normal = Vector2.fromRadians(angle);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
position: this.center.add(normal.scale(this.radius)),
|
|
73
|
+
tangent: this.counter ? normal : normal.flipped,
|
|
74
|
+
normal: this.counter ? normal : normal.flipped,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public toSVGCommands(from = 0, to = 1, move = false): string {
|
|
79
|
+
const startPos = this.getPoint(from).position;
|
|
80
|
+
const endPos = this.getPoint(to).position;
|
|
81
|
+
|
|
82
|
+
const commands: string[] = [];
|
|
83
|
+
if (move) {
|
|
84
|
+
commands.push(`M ${startPos.x} ${startPos.y}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (Math.abs(this.angle) > 0.0001) {
|
|
88
|
+
const angleCovered = (to - from) * Math.abs(this.angle);
|
|
89
|
+
const largeArc = angleCovered > Math.PI ? 1 : 0;
|
|
90
|
+
const sweep = this.counter ? 0 : 1;
|
|
91
|
+
|
|
92
|
+
commands.push(
|
|
93
|
+
`A ${this.radius} ${this.radius} 0 ${largeArc} ${sweep} ${endPos.x} ${endPos.y}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return commands.join(' ');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {Vector2, lazy} from '@canvas-commons/core';
|
|
2
|
+
import {bezierCurveTo} from '../utils';
|
|
3
|
+
import {Polynomial2D} from './Polynomial2D';
|
|
4
|
+
import {PolynomialSegment} from './PolynomialSegment';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A spline segment representing a cubic Bézier curve.
|
|
8
|
+
*/
|
|
9
|
+
export class CubicBezierSegment extends PolynomialSegment {
|
|
10
|
+
@lazy(() => document.createElementNS('http://www.w3.org/2000/svg', 'path'))
|
|
11
|
+
private static el: SVGPathElement;
|
|
12
|
+
|
|
13
|
+
public get points(): Vector2[] {
|
|
14
|
+
return [this.p0, this.p1, this.p2, this.p3];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public constructor(
|
|
18
|
+
public readonly p0: Vector2,
|
|
19
|
+
public readonly p1: Vector2,
|
|
20
|
+
public readonly p2: Vector2,
|
|
21
|
+
public readonly p3: Vector2,
|
|
22
|
+
) {
|
|
23
|
+
super(
|
|
24
|
+
new Polynomial2D(
|
|
25
|
+
p0,
|
|
26
|
+
// 3*(-p0+p1)
|
|
27
|
+
p0.flipped.add(p1).scale(3),
|
|
28
|
+
// 3*p0-6*p1+3*p2
|
|
29
|
+
p0.scale(3).sub(p1.scale(6)).add(p2.scale(3)),
|
|
30
|
+
// -p0+3*p1-3*p2+p3
|
|
31
|
+
p0.flipped.add(p1.scale(3)).sub(p2.scale(3)).add(p3),
|
|
32
|
+
),
|
|
33
|
+
CubicBezierSegment.getLength(p0, p1, p2, p3),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public split(t: number): [PolynomialSegment, PolynomialSegment] {
|
|
38
|
+
const a = new Vector2(
|
|
39
|
+
this.p0.x + (this.p1.x - this.p0.x) * t,
|
|
40
|
+
this.p0.y + (this.p1.y - this.p0.y) * t,
|
|
41
|
+
);
|
|
42
|
+
const b = new Vector2(
|
|
43
|
+
this.p1.x + (this.p2.x - this.p1.x) * t,
|
|
44
|
+
this.p1.y + (this.p2.y - this.p1.y) * t,
|
|
45
|
+
);
|
|
46
|
+
const c = new Vector2(
|
|
47
|
+
this.p2.x + (this.p3.x - this.p2.x) * t,
|
|
48
|
+
this.p2.y + (this.p3.y - this.p2.y) * t,
|
|
49
|
+
);
|
|
50
|
+
const d = new Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
|
51
|
+
const e = new Vector2(b.x + (c.x - b.x) * t, b.y + (c.y - b.y) * t);
|
|
52
|
+
const p = new Vector2(d.x + (e.x - d.x) * t, d.y + (e.y - d.y) * t);
|
|
53
|
+
|
|
54
|
+
const left = new CubicBezierSegment(this.p0, a, d, p);
|
|
55
|
+
const right = new CubicBezierSegment(p, e, c, this.p3);
|
|
56
|
+
|
|
57
|
+
return [left, right];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected override doDraw(context: CanvasRenderingContext2D | Path2D) {
|
|
61
|
+
bezierCurveTo(context, this.p1, this.p2, this.p3);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected override doSVGCommands(): string {
|
|
65
|
+
return `C ${this.p1.x} ${this.p1.y} ${this.p2.x} ${this.p2.y} ${this.p3.x} ${this.p3.y}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected static getLength(
|
|
69
|
+
p0: Vector2,
|
|
70
|
+
p1: Vector2,
|
|
71
|
+
p2: Vector2,
|
|
72
|
+
p3: Vector2,
|
|
73
|
+
): number {
|
|
74
|
+
CubicBezierSegment.el.setAttribute(
|
|
75
|
+
'd',
|
|
76
|
+
`M ${p0.x} ${p0.y} C ${p1.x} ${p1.y} ${p2.x} ${p2.y} ${p3.x} ${p3.y}`,
|
|
77
|
+
);
|
|
78
|
+
return CubicBezierSegment.el.getTotalLength();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type {Vector2} from '@canvas-commons/core';
|
|
2
|
+
|
|
3
|
+
export interface CurvePoint {
|
|
4
|
+
position: Vector2;
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated
|
|
7
|
+
* The tangent is currently inconsistent for different types of curves and may
|
|
8
|
+
* sometimes return the normal of the point, instead. This will be fixed in
|
|
9
|
+
* the next major version but is kept as is for now for backwards
|
|
10
|
+
* compatibility reasons. To always get the real tangent of the point, you can
|
|
11
|
+
* use `normal.flipped.perpendicular`, instead.
|
|
12
|
+
*/
|
|
13
|
+
tangent: Vector2;
|
|
14
|
+
normal: Vector2;
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {Segment} from './Segment';
|
|
2
|
+
|
|
3
|
+
export interface CurveProfile {
|
|
4
|
+
arcLength: number;
|
|
5
|
+
segments: Segment[];
|
|
6
|
+
minSin: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert a curve profile to SVG path data.
|
|
11
|
+
*
|
|
12
|
+
* @param profile - The curve profile to convert
|
|
13
|
+
* @returns SVG path data string
|
|
14
|
+
*/
|
|
15
|
+
export function profileToSVGPathData(profile: CurveProfile): string {
|
|
16
|
+
if (profile.segments.length === 0) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const commands: string[] = [];
|
|
21
|
+
for (let i = 0; i < profile.segments.length; i++) {
|
|
22
|
+
const segment = profile.segments[i];
|
|
23
|
+
const move = i === 0;
|
|
24
|
+
commands.push(segment.toSVGCommands(0, 1, move));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return commands.join(' ');
|
|
28
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {Vector2} from '@canvas-commons/core';
|
|
2
|
+
import {lineTo, moveTo} from '../utils';
|
|
3
|
+
import {CurvePoint} from './CurvePoint';
|
|
4
|
+
import {Segment} from './Segment';
|
|
5
|
+
|
|
6
|
+
export class LineSegment extends Segment {
|
|
7
|
+
private readonly length: number;
|
|
8
|
+
private readonly vector: Vector2;
|
|
9
|
+
private readonly normal: Vector2;
|
|
10
|
+
public override readonly points: Vector2[];
|
|
11
|
+
|
|
12
|
+
public constructor(
|
|
13
|
+
public readonly from: Vector2,
|
|
14
|
+
public readonly to: Vector2,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
this.vector = to.sub(from);
|
|
18
|
+
this.length = this.vector.magnitude;
|
|
19
|
+
this.normal = this.vector.perpendicular.normalized.safe;
|
|
20
|
+
this.points = [from, to];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public get arcLength(): number {
|
|
24
|
+
return this.length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public draw(
|
|
28
|
+
context: CanvasRenderingContext2D | Path2D,
|
|
29
|
+
start = 0,
|
|
30
|
+
end = 1,
|
|
31
|
+
move = false,
|
|
32
|
+
): [CurvePoint, CurvePoint] {
|
|
33
|
+
const from = this.from.add(this.vector.scale(start));
|
|
34
|
+
const to = this.from.add(this.vector.scale(end));
|
|
35
|
+
if (move) {
|
|
36
|
+
moveTo(context, from);
|
|
37
|
+
}
|
|
38
|
+
lineTo(context, to);
|
|
39
|
+
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
position: from,
|
|
43
|
+
tangent: this.normal.flipped,
|
|
44
|
+
normal: this.normal,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
position: to,
|
|
48
|
+
tangent: this.normal,
|
|
49
|
+
normal: this.normal,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public getPoint(distance: number): CurvePoint {
|
|
55
|
+
const point = this.from.add(this.vector.scale(distance));
|
|
56
|
+
return {
|
|
57
|
+
position: point,
|
|
58
|
+
tangent: this.normal.flipped,
|
|
59
|
+
normal: this.normal,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public toSVGCommands(start = 0, end = 1, move = false): string {
|
|
64
|
+
const from = this.from.add(this.vector.scale(start));
|
|
65
|
+
const to = this.from.add(this.vector.scale(end));
|
|
66
|
+
const commands: string[] = [];
|
|
67
|
+
|
|
68
|
+
if (move) {
|
|
69
|
+
commands.push(`M ${from.x} ${from.y}`);
|
|
70
|
+
}
|
|
71
|
+
commands.push(`L ${to.x} ${to.y}`);
|
|
72
|
+
|
|
73
|
+
return commands.join(' ');
|
|
74
|
+
}
|
|
75
|
+
}
|