js-draw 0.0.1
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/.eslintrc.js +57 -0
- package/.husky/pre-commit +4 -0
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/__mocks__/coloris.ts +8 -0
- package/__mocks__/styleMock.js +1 -0
- package/dist/__mocks__/coloris.d.ts +2 -0
- package/dist/__mocks__/coloris.js +5 -0
- package/dist/build_tools/BundledFile.d.ts +12 -0
- package/dist/build_tools/BundledFile.js +153 -0
- package/dist/scripts/bundle.d.ts +1 -0
- package/dist/scripts/bundle.js +19 -0
- package/dist/scripts/watchBundle.d.ts +1 -0
- package/dist/scripts/watchBundle.js +9 -0
- package/dist/src/Color4.d.ts +23 -0
- package/dist/src/Color4.js +102 -0
- package/dist/src/Display.d.ts +22 -0
- package/dist/src/Display.js +93 -0
- package/dist/src/Editor.d.ts +55 -0
- package/dist/src/Editor.js +366 -0
- package/dist/src/EditorImage.d.ts +44 -0
- package/dist/src/EditorImage.js +243 -0
- package/dist/src/EventDispatcher.d.ts +11 -0
- package/dist/src/EventDispatcher.js +39 -0
- package/dist/src/Pointer.d.ts +22 -0
- package/dist/src/Pointer.js +57 -0
- package/dist/src/SVGLoader.d.ts +21 -0
- package/dist/src/SVGLoader.js +204 -0
- package/dist/src/StrokeBuilder.d.ts +35 -0
- package/dist/src/StrokeBuilder.js +275 -0
- package/dist/src/UndoRedoHistory.d.ts +17 -0
- package/dist/src/UndoRedoHistory.js +46 -0
- package/dist/src/Viewport.d.ts +39 -0
- package/dist/src/Viewport.js +134 -0
- package/dist/src/commands/Command.d.ts +15 -0
- package/dist/src/commands/Command.js +29 -0
- package/dist/src/commands/Erase.d.ts +11 -0
- package/dist/src/commands/Erase.js +37 -0
- package/dist/src/commands/localization.d.ts +19 -0
- package/dist/src/commands/localization.js +17 -0
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +46 -0
- package/dist/src/components/Stroke.d.ts +16 -0
- package/dist/src/components/Stroke.js +79 -0
- package/dist/src/components/UnknownSVGObject.d.ts +15 -0
- package/dist/src/components/UnknownSVGObject.js +25 -0
- package/dist/src/components/localization.d.ts +5 -0
- package/dist/src/components/localization.js +4 -0
- package/dist/src/geometry/LineSegment2.d.ts +19 -0
- package/dist/src/geometry/LineSegment2.js +100 -0
- package/dist/src/geometry/Mat33.d.ts +31 -0
- package/dist/src/geometry/Mat33.js +187 -0
- package/dist/src/geometry/Path.d.ts +55 -0
- package/dist/src/geometry/Path.js +364 -0
- package/dist/src/geometry/Rect2.d.ts +47 -0
- package/dist/src/geometry/Rect2.js +148 -0
- package/dist/src/geometry/Vec2.d.ts +13 -0
- package/dist/src/geometry/Vec2.js +13 -0
- package/dist/src/geometry/Vec3.d.ts +32 -0
- package/dist/src/geometry/Vec3.js +98 -0
- package/dist/src/localization.d.ts +12 -0
- package/dist/src/localization.js +5 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +4 -0
- package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
- package/dist/src/rendering/AbstractRenderer.js +108 -0
- package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
- package/dist/src/rendering/CanvasRenderer.js +108 -0
- package/dist/src/rendering/DummyRenderer.d.ts +25 -0
- package/dist/src/rendering/DummyRenderer.js +65 -0
- package/dist/src/rendering/SVGRenderer.d.ts +27 -0
- package/dist/src/rendering/SVGRenderer.js +122 -0
- package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
- package/dist/src/testing/loadExpectExtensions.js +27 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
- package/dist/src/toolbar/HTMLToolbar.js +444 -0
- package/dist/src/toolbar/types.d.ts +17 -0
- package/dist/src/toolbar/types.js +5 -0
- package/dist/src/tools/BaseTool.d.ts +20 -0
- package/dist/src/tools/BaseTool.js +44 -0
- package/dist/src/tools/Eraser.d.ts +16 -0
- package/dist/src/tools/Eraser.js +53 -0
- package/dist/src/tools/PanZoom.d.ts +40 -0
- package/dist/src/tools/PanZoom.js +191 -0
- package/dist/src/tools/Pen.d.ts +25 -0
- package/dist/src/tools/Pen.js +97 -0
- package/dist/src/tools/SelectionTool.d.ts +49 -0
- package/dist/src/tools/SelectionTool.js +437 -0
- package/dist/src/tools/ToolController.d.ts +18 -0
- package/dist/src/tools/ToolController.js +110 -0
- package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
- package/dist/src/tools/ToolEnabledGroup.js +11 -0
- package/dist/src/tools/localization.d.ts +10 -0
- package/dist/src/tools/localization.js +9 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.js +20 -0
- package/jest.config.js +22 -0
- package/lint-staged.config.js +6 -0
- package/package.json +82 -0
- package/src/Color4.test.ts +12 -0
- package/src/Color4.ts +122 -0
- package/src/Display.ts +118 -0
- package/src/Editor.css +58 -0
- package/src/Editor.ts +469 -0
- package/src/EditorImage.test.ts +90 -0
- package/src/EditorImage.ts +297 -0
- package/src/EventDispatcher.test.ts +123 -0
- package/src/EventDispatcher.ts +53 -0
- package/src/Pointer.ts +93 -0
- package/src/SVGLoader.ts +230 -0
- package/src/StrokeBuilder.ts +362 -0
- package/src/UndoRedoHistory.ts +61 -0
- package/src/Viewport.ts +168 -0
- package/src/commands/Command.ts +43 -0
- package/src/commands/Erase.ts +52 -0
- package/src/commands/localization.ts +38 -0
- package/src/components/AbstractComponent.ts +73 -0
- package/src/components/Stroke.test.ts +18 -0
- package/src/components/Stroke.ts +102 -0
- package/src/components/UnknownSVGObject.ts +36 -0
- package/src/components/localization.ts +9 -0
- package/src/editorStyles.js +3 -0
- package/src/geometry/LineSegment2.test.ts +77 -0
- package/src/geometry/LineSegment2.ts +127 -0
- package/src/geometry/Mat33.test.ts +144 -0
- package/src/geometry/Mat33.ts +268 -0
- package/src/geometry/Path.fromString.test.ts +146 -0
- package/src/geometry/Path.test.ts +96 -0
- package/src/geometry/Path.toString.test.ts +31 -0
- package/src/geometry/Path.ts +456 -0
- package/src/geometry/Rect2.test.ts +121 -0
- package/src/geometry/Rect2.ts +215 -0
- package/src/geometry/Vec2.test.ts +32 -0
- package/src/geometry/Vec2.ts +18 -0
- package/src/geometry/Vec3.test.ts +29 -0
- package/src/geometry/Vec3.ts +133 -0
- package/src/localization.ts +27 -0
- package/src/rendering/AbstractRenderer.ts +164 -0
- package/src/rendering/CanvasRenderer.ts +141 -0
- package/src/rendering/DummyRenderer.ts +80 -0
- package/src/rendering/SVGRenderer.ts +159 -0
- package/src/testing/loadExpectExtensions.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +551 -0
- package/src/toolbar/toolbar.css +110 -0
- package/src/toolbar/types.ts +20 -0
- package/src/tools/BaseTool.ts +58 -0
- package/src/tools/Eraser.ts +67 -0
- package/src/tools/PanZoom.ts +253 -0
- package/src/tools/Pen.ts +121 -0
- package/src/tools/SelectionTool.test.ts +85 -0
- package/src/tools/SelectionTool.ts +545 -0
- package/src/tools/ToolController.ts +126 -0
- package/src/tools/ToolEnabledGroup.ts +14 -0
- package/src/tools/localization.ts +22 -0
- package/src/types.ts +133 -0
- package/tsconfig.json +28 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
import EditorImage from '../EditorImage';
|
2
|
+
export default class AbstractComponent {
|
3
|
+
constructor() {
|
4
|
+
this.lastChangedTime = (new Date()).getTime();
|
5
|
+
this.zIndex = AbstractComponent.zIndexCounter++;
|
6
|
+
}
|
7
|
+
getBBox() {
|
8
|
+
return this.contentBBox;
|
9
|
+
}
|
10
|
+
// Returns a command that, when applied, transforms this by [affineTransfm] and
|
11
|
+
// updates the editor.
|
12
|
+
transformBy(affineTransfm) {
|
13
|
+
const updateTransform = (editor, newTransfm) => {
|
14
|
+
// Any parent should have only one direct child.
|
15
|
+
const parent = editor.image.findParent(this);
|
16
|
+
let hadParent = false;
|
17
|
+
if (parent) {
|
18
|
+
parent.remove();
|
19
|
+
hadParent = true;
|
20
|
+
}
|
21
|
+
this.applyTransformation(newTransfm);
|
22
|
+
// Add the element back to the document.
|
23
|
+
if (hadParent) {
|
24
|
+
new EditorImage.AddElementCommand(this).apply(editor);
|
25
|
+
}
|
26
|
+
};
|
27
|
+
const origZIndex = this.zIndex;
|
28
|
+
return {
|
29
|
+
apply: (editor) => {
|
30
|
+
this.zIndex = AbstractComponent.zIndexCounter++;
|
31
|
+
updateTransform(editor, affineTransfm);
|
32
|
+
editor.queueRerender();
|
33
|
+
},
|
34
|
+
unapply: (editor) => {
|
35
|
+
this.zIndex = origZIndex;
|
36
|
+
updateTransform(editor, affineTransfm.inverse());
|
37
|
+
editor.queueRerender();
|
38
|
+
},
|
39
|
+
description(localizationTable) {
|
40
|
+
return localizationTable.transformedElements(1);
|
41
|
+
},
|
42
|
+
};
|
43
|
+
}
|
44
|
+
}
|
45
|
+
// Topmost z-index
|
46
|
+
AbstractComponent.zIndexCounter = 0;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import LineSegment2 from '../geometry/LineSegment2';
|
2
|
+
import Mat33 from '../geometry/Mat33';
|
3
|
+
import Rect2 from '../geometry/Rect2';
|
4
|
+
import AbstractRenderer, { RenderablePathSpec } from '../rendering/AbstractRenderer';
|
5
|
+
import AbstractComponent from './AbstractComponent';
|
6
|
+
import { ImageComponentLocalization } from './localization';
|
7
|
+
export default class Stroke extends AbstractComponent {
|
8
|
+
private parts;
|
9
|
+
protected contentBBox: Rect2;
|
10
|
+
constructor(parts: RenderablePathSpec[]);
|
11
|
+
intersects(line: LineSegment2): boolean;
|
12
|
+
render(canvas: AbstractRenderer, visibleRect: Rect2): void;
|
13
|
+
private bboxForPart;
|
14
|
+
protected applyTransformation(affineTransfm: Mat33): void;
|
15
|
+
description(localization: ImageComponentLocalization): string;
|
16
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import Path from '../geometry/Path';
|
2
|
+
import Rect2 from '../geometry/Rect2';
|
3
|
+
import AbstractComponent from './AbstractComponent';
|
4
|
+
export default class Stroke extends AbstractComponent {
|
5
|
+
constructor(parts) {
|
6
|
+
var _a;
|
7
|
+
super();
|
8
|
+
this.parts = parts.map(section => {
|
9
|
+
const path = Path.fromRenderable(section);
|
10
|
+
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
11
|
+
if (!this.contentBBox) {
|
12
|
+
this.contentBBox = pathBBox;
|
13
|
+
}
|
14
|
+
else {
|
15
|
+
this.contentBBox = this.contentBBox.union(pathBBox);
|
16
|
+
}
|
17
|
+
return {
|
18
|
+
path,
|
19
|
+
bbox: pathBBox,
|
20
|
+
// To implement RenderablePathSpec
|
21
|
+
startPoint: path.startPoint,
|
22
|
+
style: section.style,
|
23
|
+
commands: path.parts,
|
24
|
+
};
|
25
|
+
});
|
26
|
+
(_a = this.contentBBox) !== null && _a !== void 0 ? _a : (this.contentBBox = Rect2.empty);
|
27
|
+
}
|
28
|
+
intersects(line) {
|
29
|
+
for (const part of this.parts) {
|
30
|
+
if (part.path.intersection(line).length > 0) {
|
31
|
+
return true;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
return false;
|
35
|
+
}
|
36
|
+
render(canvas, visibleRect) {
|
37
|
+
canvas.startObject(this.getBBox());
|
38
|
+
for (const part of this.parts) {
|
39
|
+
const bbox = part.bbox;
|
40
|
+
if (bbox.intersects(visibleRect)) {
|
41
|
+
canvas.drawPath(part);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
canvas.endObject();
|
45
|
+
}
|
46
|
+
// Grows the bounding box for a given stroke part based on that part's style.
|
47
|
+
bboxForPart(origBBox, style) {
|
48
|
+
if (!style.stroke) {
|
49
|
+
return origBBox;
|
50
|
+
}
|
51
|
+
return origBBox.grownBy(style.stroke.width / 2);
|
52
|
+
}
|
53
|
+
applyTransformation(affineTransfm) {
|
54
|
+
this.contentBBox = Rect2.empty;
|
55
|
+
let isFirstPart = true;
|
56
|
+
// Update each part
|
57
|
+
this.parts = this.parts.map((part) => {
|
58
|
+
const newPath = part.path.transformedBy(affineTransfm);
|
59
|
+
const newBBox = this.bboxForPart(newPath.bbox, part.style);
|
60
|
+
if (isFirstPart) {
|
61
|
+
this.contentBBox = newBBox;
|
62
|
+
isFirstPart = false;
|
63
|
+
}
|
64
|
+
else {
|
65
|
+
this.contentBBox = this.contentBBox.union(newBBox);
|
66
|
+
}
|
67
|
+
return {
|
68
|
+
path: newPath,
|
69
|
+
bbox: newBBox,
|
70
|
+
startPoint: newPath.startPoint,
|
71
|
+
commands: newPath.parts,
|
72
|
+
style: part.style,
|
73
|
+
};
|
74
|
+
});
|
75
|
+
}
|
76
|
+
description(localization) {
|
77
|
+
return localization.stroke;
|
78
|
+
}
|
79
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import LineSegment2 from '../geometry/LineSegment2';
|
2
|
+
import Mat33 from '../geometry/Mat33';
|
3
|
+
import Rect2 from '../geometry/Rect2';
|
4
|
+
import AbstractRenderer from '../rendering/AbstractRenderer';
|
5
|
+
import AbstractComponent from './AbstractComponent';
|
6
|
+
import { ImageComponentLocalization } from './localization';
|
7
|
+
export default class UnknownSVGObject extends AbstractComponent {
|
8
|
+
private svgObject;
|
9
|
+
protected contentBBox: Rect2;
|
10
|
+
constructor(svgObject: SVGElement);
|
11
|
+
render(canvas: AbstractRenderer, _visibleRect: Rect2): void;
|
12
|
+
intersects(lineSegment: LineSegment2): boolean;
|
13
|
+
protected applyTransformation(_affineTransfm: Mat33): void;
|
14
|
+
description(localization: ImageComponentLocalization): string;
|
15
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import Rect2 from '../geometry/Rect2';
|
2
|
+
import SVGRenderer from '../rendering/SVGRenderer';
|
3
|
+
import AbstractComponent from './AbstractComponent';
|
4
|
+
export default class UnknownSVGObject extends AbstractComponent {
|
5
|
+
constructor(svgObject) {
|
6
|
+
super();
|
7
|
+
this.svgObject = svgObject;
|
8
|
+
this.contentBBox = Rect2.of(svgObject.getBoundingClientRect());
|
9
|
+
}
|
10
|
+
render(canvas, _visibleRect) {
|
11
|
+
if (!(canvas instanceof SVGRenderer)) {
|
12
|
+
// Don't draw unrenderable objects if we can't
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
canvas.drawSVGElem(this.svgObject);
|
16
|
+
}
|
17
|
+
intersects(lineSegment) {
|
18
|
+
return this.contentBBox.getEdges().some(edge => edge.intersection(lineSegment) !== null);
|
19
|
+
}
|
20
|
+
applyTransformation(_affineTransfm) {
|
21
|
+
}
|
22
|
+
description(localization) {
|
23
|
+
return localization.svgObject;
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import Rect2 from './Rect2';
|
2
|
+
import { Vec2, Point2 } from './Vec2';
|
3
|
+
interface IntersectionResult {
|
4
|
+
point: Point2;
|
5
|
+
t: number;
|
6
|
+
}
|
7
|
+
export default class LineSegment2 {
|
8
|
+
private readonly point1;
|
9
|
+
private readonly point2;
|
10
|
+
readonly direction: Vec2;
|
11
|
+
readonly length: number;
|
12
|
+
readonly bbox: Rect2;
|
13
|
+
constructor(point1: Point2, point2: Point2);
|
14
|
+
get p1(): Point2;
|
15
|
+
get p2(): Point2;
|
16
|
+
get(t: number): Point2;
|
17
|
+
intersection(other: LineSegment2): IntersectionResult | null;
|
18
|
+
}
|
19
|
+
export {};
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import Rect2 from './Rect2';
|
2
|
+
import { Vec2 } from './Vec2';
|
3
|
+
export default class LineSegment2 {
|
4
|
+
constructor(point1, point2) {
|
5
|
+
this.point1 = point1;
|
6
|
+
this.point2 = point2;
|
7
|
+
this.bbox = Rect2.bboxOf([point1, point2]);
|
8
|
+
this.direction = point2.minus(point1);
|
9
|
+
this.length = this.direction.magnitude();
|
10
|
+
// Normalize
|
11
|
+
if (this.length > 0) {
|
12
|
+
this.direction = this.direction.times(1 / this.length);
|
13
|
+
}
|
14
|
+
}
|
15
|
+
// Accessors to make LineSegment2 compatible with bezier-js's
|
16
|
+
// interface
|
17
|
+
get p1() {
|
18
|
+
return this.point1;
|
19
|
+
}
|
20
|
+
get p2() {
|
21
|
+
return this.point2;
|
22
|
+
}
|
23
|
+
get(t) {
|
24
|
+
return this.point1.plus(this.direction.times(t));
|
25
|
+
}
|
26
|
+
intersection(other) {
|
27
|
+
// We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
|
28
|
+
// Observe that
|
29
|
+
// x = this.point1.x + this.direction.x · t₁
|
30
|
+
// = other.point1.x + other.direction.x · t₂
|
31
|
+
// Thus,
|
32
|
+
// t₁ = (x - this.point1.x) / this.direction.x
|
33
|
+
// = (y - this.point1.y) / this.direction.y
|
34
|
+
// and
|
35
|
+
// t₂ = (x - other.point1.x) / other.direction.x
|
36
|
+
// (and similarly for y)
|
37
|
+
//
|
38
|
+
// Letting o₁ₓ = this.point1.x, o₂ₓ = other.point1.x,
|
39
|
+
// d₁ᵧ = this.direction.y, ...
|
40
|
+
//
|
41
|
+
// We can substitute these into the equations for y:
|
42
|
+
// y = o₁ᵧ + d₁ᵧ · (x - o₁ₓ) / d₁ₓ
|
43
|
+
// = o₂ᵧ + d₂ᵧ · (x - o₂ₓ) / d₂ₓ
|
44
|
+
// ⇒ o₁ᵧ - o₂ᵧ = d₂ᵧ · (x - o₂ₓ) / d₂ₓ - d₁ᵧ · (x - o₁ₓ) / d₁ₓ
|
45
|
+
// = (d₂ᵧ/d₂ₓ)(x) - (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(x) + (d₁ᵧ/d₁ₓ)(o₁ₓ)
|
46
|
+
// = (x)(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ) - (d₂ᵧ/d₂ₓ)(o₂ₓ) + (d₁ᵧ/d₁ₓ)(o₁ₓ)
|
47
|
+
// ⇒ (x)(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ) = o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ)
|
48
|
+
// ⇒ x = (o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ))/(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ)
|
49
|
+
// = (d₁ₓd₂ₓ)(o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
|
50
|
+
// = ((o₁ᵧ - o₂ᵧ)((d₁ₓd₂ₓ)) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
|
51
|
+
// ⇒ y = o₁ᵧ + d₁ᵧ · (x - o₁ₓ) / d₁ₓ = ...
|
52
|
+
let resultPoint, resultT;
|
53
|
+
if (this.direction.x === 0) {
|
54
|
+
// Vertical line: Where does the other have x = this.point1.x?
|
55
|
+
// x = o₁ₓ = o₂ₓ + d₂ₓ · (y - o₂ᵧ) / d₂ᵧ
|
56
|
+
// ⇒ (o₁ₓ - o₂ₓ)(d₂ᵧ/d₂ₓ) + o₂ᵧ = y
|
57
|
+
// Avoid division by zero
|
58
|
+
if (other.direction.x === 0 || this.direction.y === 0) {
|
59
|
+
return null;
|
60
|
+
}
|
61
|
+
const xIntersect = this.point1.x;
|
62
|
+
const yIntersect = (this.point1.x - other.point1.x) * other.direction.y / other.direction.x + other.point1.y;
|
63
|
+
resultPoint = Vec2.of(xIntersect, yIntersect);
|
64
|
+
resultT = (yIntersect - this.point1.y) / this.direction.y;
|
65
|
+
}
|
66
|
+
else {
|
67
|
+
// From above,
|
68
|
+
// x = ((o₁ᵧ - o₂ᵧ)(d₁ₓd₂ₓ) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
|
69
|
+
const numerator = ((this.point1.y - other.point1.y) * this.direction.x * other.direction.x
|
70
|
+
+ this.direction.x * other.direction.y * other.point1.x
|
71
|
+
- this.direction.y * other.direction.x * this.point1.x);
|
72
|
+
const denominator = (other.direction.y * this.direction.x
|
73
|
+
- this.direction.y * other.direction.x);
|
74
|
+
// Avoid dividing by zero. It means there is no intersection
|
75
|
+
if (denominator === 0) {
|
76
|
+
return null;
|
77
|
+
}
|
78
|
+
const xIntersect = numerator / denominator;
|
79
|
+
const t1 = (xIntersect - this.point1.x) / this.direction.x;
|
80
|
+
const yIntersect = this.point1.y + this.direction.y * t1;
|
81
|
+
resultPoint = Vec2.of(xIntersect, yIntersect);
|
82
|
+
resultT = (xIntersect - this.point1.x) / this.direction.x;
|
83
|
+
}
|
84
|
+
// Ensure the result is in this/the other segment.
|
85
|
+
const resultToP1 = resultPoint.minus(this.point1).magnitude();
|
86
|
+
const resultToP2 = resultPoint.minus(this.point2).magnitude();
|
87
|
+
const resultToP3 = resultPoint.minus(other.point1).magnitude();
|
88
|
+
const resultToP4 = resultPoint.minus(other.point2).magnitude();
|
89
|
+
if (resultToP1 > this.length
|
90
|
+
|| resultToP2 > this.length
|
91
|
+
|| resultToP3 > other.length
|
92
|
+
|| resultToP4 > other.length) {
|
93
|
+
return null;
|
94
|
+
}
|
95
|
+
return {
|
96
|
+
point: resultPoint,
|
97
|
+
t: resultT,
|
98
|
+
};
|
99
|
+
}
|
100
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { Point2, Vec2 } from './Vec2';
|
2
|
+
import Vec3 from './Vec3';
|
3
|
+
export default class Mat33 {
|
4
|
+
readonly a1: number;
|
5
|
+
readonly a2: number;
|
6
|
+
readonly a3: number;
|
7
|
+
readonly b1: number;
|
8
|
+
readonly b2: number;
|
9
|
+
readonly b3: number;
|
10
|
+
readonly c1: number;
|
11
|
+
readonly c2: number;
|
12
|
+
readonly c3: number;
|
13
|
+
private readonly rows;
|
14
|
+
constructor(a1: number, a2: number, a3: number, b1: number, b2: number, b3: number, c1: number, c2: number, c3: number);
|
15
|
+
static ofRows(r1: Vec3, r2: Vec3, r3: Vec3): Mat33;
|
16
|
+
static identity: Mat33;
|
17
|
+
inverse(): Mat33;
|
18
|
+
invertable(): boolean;
|
19
|
+
private cachedInverse;
|
20
|
+
private computeInverse;
|
21
|
+
transposed(): Mat33;
|
22
|
+
rightMul(other: Mat33): Mat33;
|
23
|
+
transformVec2(other: Vec3): Vec2;
|
24
|
+
transformVec3(other: Vec3): Vec3;
|
25
|
+
eq(other: Mat33, fuzz?: number): boolean;
|
26
|
+
toString(): string;
|
27
|
+
toArray(): number[];
|
28
|
+
static translation(amount: Vec2): Mat33;
|
29
|
+
static zRotation(radians: number, center?: Point2): Mat33;
|
30
|
+
static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
|
31
|
+
}
|
@@ -0,0 +1,187 @@
|
|
1
|
+
import { Vec2 } from './Vec2';
|
2
|
+
import Vec3 from './Vec3';
|
3
|
+
// Represents a three dimensional linear transformation or
|
4
|
+
// a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
5
|
+
// **and** translates while a linear transformation just scales/rotates/shears).
|
6
|
+
export default class Mat33 {
|
7
|
+
constructor(a1, a2, a3, b1, b2, b3, c1, c2, c3) {
|
8
|
+
this.a1 = a1;
|
9
|
+
this.a2 = a2;
|
10
|
+
this.a3 = a3;
|
11
|
+
this.b1 = b1;
|
12
|
+
this.b2 = b2;
|
13
|
+
this.b3 = b3;
|
14
|
+
this.c1 = c1;
|
15
|
+
this.c2 = c2;
|
16
|
+
this.c3 = c3;
|
17
|
+
this.cachedInverse = undefined;
|
18
|
+
this.rows = [
|
19
|
+
Vec3.of(a1, a2, a3),
|
20
|
+
Vec3.of(b1, b2, b3),
|
21
|
+
Vec3.of(c1, c2, c3),
|
22
|
+
];
|
23
|
+
}
|
24
|
+
static ofRows(r1, r2, r3) {
|
25
|
+
return new Mat33(r1.x, r1.y, r1.z, r2.x, r2.y, r2.z, r3.x, r3.y, r3.z);
|
26
|
+
}
|
27
|
+
// Either returns the inverse of this, or, if this matrix is singular/uninvertable,
|
28
|
+
// returns Mat33.identity.
|
29
|
+
inverse() {
|
30
|
+
var _a;
|
31
|
+
return (_a = this.computeInverse()) !== null && _a !== void 0 ? _a : Mat33.identity;
|
32
|
+
}
|
33
|
+
invertable() {
|
34
|
+
return this.computeInverse() !== null;
|
35
|
+
}
|
36
|
+
computeInverse() {
|
37
|
+
if (this.cachedInverse !== undefined) {
|
38
|
+
return this.cachedInverse;
|
39
|
+
}
|
40
|
+
const toIdentity = [
|
41
|
+
this.rows[0],
|
42
|
+
this.rows[1],
|
43
|
+
this.rows[2],
|
44
|
+
];
|
45
|
+
const toResult = [
|
46
|
+
Vec3.unitX,
|
47
|
+
Vec3.unitY,
|
48
|
+
Vec3.unitZ,
|
49
|
+
];
|
50
|
+
// Convert toIdentity to the identity matrix and
|
51
|
+
// toResult to the inverse through elementary row operations
|
52
|
+
for (let cursor = 0; cursor < 3; cursor++) {
|
53
|
+
// Select the [cursor]th diagonal entry
|
54
|
+
let pivot = toIdentity[cursor].at(cursor);
|
55
|
+
// Don't divide by zero (treat very small numbers as zero).
|
56
|
+
const minDivideBy = 1e-10;
|
57
|
+
if (Math.abs(pivot) < minDivideBy) {
|
58
|
+
let swapIndex = -1;
|
59
|
+
// For all other rows,
|
60
|
+
for (let i = 1; i <= 2; i++) {
|
61
|
+
const otherRowIdx = (cursor + i) % 3;
|
62
|
+
if (Math.abs(toIdentity[otherRowIdx].at(cursor)) >= minDivideBy) {
|
63
|
+
swapIndex = otherRowIdx;
|
64
|
+
break;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
// Can't swap with another row?
|
68
|
+
if (swapIndex === -1) {
|
69
|
+
this.cachedInverse = null;
|
70
|
+
return null;
|
71
|
+
}
|
72
|
+
const tmpIdentityRow = toIdentity[cursor];
|
73
|
+
const tmpResultRow = toResult[cursor];
|
74
|
+
// Swap!
|
75
|
+
toIdentity[cursor] = toIdentity[swapIndex];
|
76
|
+
toResult[cursor] = toResult[swapIndex];
|
77
|
+
toIdentity[swapIndex] = tmpIdentityRow;
|
78
|
+
toResult[swapIndex] = tmpResultRow;
|
79
|
+
pivot = toIdentity[cursor].at(cursor);
|
80
|
+
}
|
81
|
+
// Make toIdentity[k = cursor] = 1
|
82
|
+
let scale = 1.0 / pivot;
|
83
|
+
toIdentity[cursor] = toIdentity[cursor].times(scale);
|
84
|
+
toResult[cursor] = toResult[cursor].times(scale);
|
85
|
+
const cursorToIdentityRow = toIdentity[cursor];
|
86
|
+
const cursorToResultRow = toResult[cursor];
|
87
|
+
// Make toIdentity[k ≠ cursor] = 0
|
88
|
+
for (let i = 1; i <= 2; i++) {
|
89
|
+
const otherRowIdx = (cursor + i) % 3;
|
90
|
+
scale = -toIdentity[otherRowIdx].at(cursor);
|
91
|
+
toIdentity[otherRowIdx] = toIdentity[otherRowIdx].plus(cursorToIdentityRow.times(scale));
|
92
|
+
toResult[otherRowIdx] = toResult[otherRowIdx].plus(cursorToResultRow.times(scale));
|
93
|
+
}
|
94
|
+
}
|
95
|
+
const inverse = Mat33.ofRows(toResult[0], toResult[1], toResult[2]);
|
96
|
+
this.cachedInverse = inverse;
|
97
|
+
return inverse;
|
98
|
+
}
|
99
|
+
transposed() {
|
100
|
+
return new Mat33(this.a1, this.b1, this.c1, this.a2, this.b2, this.c2, this.a3, this.b3, this.c3);
|
101
|
+
}
|
102
|
+
rightMul(other) {
|
103
|
+
other = other.transposed();
|
104
|
+
const at = (row, col) => {
|
105
|
+
return this.rows[row].dot(other.rows[col]);
|
106
|
+
};
|
107
|
+
return new Mat33(at(0, 0), at(0, 1), at(0, 2), at(1, 0), at(1, 1), at(1, 2), at(2, 0), at(2, 1), at(2, 2));
|
108
|
+
}
|
109
|
+
// Applies this as an affine transformation to the given vector.
|
110
|
+
// Returns a transformed version of [other].
|
111
|
+
transformVec2(other) {
|
112
|
+
// When transforming a Vec2, we want to use the z transformation
|
113
|
+
// components of this for translation:
|
114
|
+
// ⎡ . . tX ⎤
|
115
|
+
// ⎢ . . tY ⎥
|
116
|
+
// ⎣ 0 0 1 ⎦
|
117
|
+
// For this, we need other's z component to be 1 (so that tX and tY
|
118
|
+
// are scaled by 1):
|
119
|
+
let intermediate = Vec3.of(other.x, other.y, 1);
|
120
|
+
intermediate = this.transformVec3(intermediate);
|
121
|
+
// Drop the z=1 to allow magnitude to work as expected
|
122
|
+
return Vec2.of(intermediate.x, intermediate.y);
|
123
|
+
}
|
124
|
+
// Applies this as a linear transformation to the given vector (doesn't translate).
|
125
|
+
// This is the standard way of transforming vectors in ℝ³.
|
126
|
+
transformVec3(other) {
|
127
|
+
return Vec3.of(this.rows[0].dot(other), this.rows[1].dot(other), this.rows[2].dot(other));
|
128
|
+
}
|
129
|
+
// Returns true iff this = other ± fuzz
|
130
|
+
eq(other, fuzz = 0) {
|
131
|
+
for (let i = 0; i < 3; i++) {
|
132
|
+
if (!this.rows[i].eq(other.rows[i], fuzz)) {
|
133
|
+
return false;
|
134
|
+
}
|
135
|
+
}
|
136
|
+
return true;
|
137
|
+
}
|
138
|
+
toString() {
|
139
|
+
return `
|
140
|
+
⎡ ${this.a1},\t ${this.a2},\t ${this.a3}\t ⎤
|
141
|
+
⎢ ${this.b1},\t ${this.b2},\t ${this.b3}\t ⎥
|
142
|
+
⎣ ${this.c1},\t ${this.c2},\t ${this.c3}\t ⎦
|
143
|
+
`.trimRight();
|
144
|
+
}
|
145
|
+
// result[0] = top left element
|
146
|
+
// result[1] = element at row zero, column 1
|
147
|
+
// ...
|
148
|
+
toArray() {
|
149
|
+
return [
|
150
|
+
this.a1, this.a2, this.a3,
|
151
|
+
this.b1, this.b2, this.b3,
|
152
|
+
this.c1, this.c2, this.c3,
|
153
|
+
];
|
154
|
+
}
|
155
|
+
// Constructs a 3x3 translation matrix (for translating Vec2s)
|
156
|
+
static translation(amount) {
|
157
|
+
// When transforming Vec2s by a 3x3 matrix, we give the input
|
158
|
+
// Vec2s z = 1. As such,
|
159
|
+
// outVec2.x = inVec2.x * 1 + inVec2.y * 0 + 1 * amount.x
|
160
|
+
// ...
|
161
|
+
return new Mat33(1, 0, amount.x, 0, 1, amount.y, 0, 0, 1);
|
162
|
+
}
|
163
|
+
static zRotation(radians, center = Vec2.zero) {
|
164
|
+
const cos = Math.cos(radians);
|
165
|
+
const sin = Math.sin(radians);
|
166
|
+
// Translate everything so that rotation is about the origin
|
167
|
+
let result = Mat33.translation(center);
|
168
|
+
result = result.rightMul(new Mat33(cos, -sin, 0, sin, cos, 0, 0, 0, 1));
|
169
|
+
return result.rightMul(Mat33.translation(center.times(-1)));
|
170
|
+
}
|
171
|
+
static scaling2D(amount, center = Vec2.zero) {
|
172
|
+
let result = Mat33.translation(center);
|
173
|
+
let xAmount, yAmount;
|
174
|
+
if (typeof amount === 'number') {
|
175
|
+
xAmount = amount;
|
176
|
+
yAmount = amount;
|
177
|
+
}
|
178
|
+
else {
|
179
|
+
xAmount = amount.x;
|
180
|
+
yAmount = amount.y;
|
181
|
+
}
|
182
|
+
result = result.rightMul(new Mat33(xAmount, 0, 0, 0, yAmount, 0, 0, 0, 1));
|
183
|
+
// Translate such that [center] goes to (0, 0)
|
184
|
+
return result.rightMul(Mat33.translation(center.times(-1)));
|
185
|
+
}
|
186
|
+
}
|
187
|
+
Mat33.identity = new Mat33(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { Bezier } from 'bezier-js';
|
2
|
+
import { RenderingStyle, RenderablePathSpec } from '../rendering/AbstractRenderer';
|
3
|
+
import LineSegment2 from './LineSegment2';
|
4
|
+
import Mat33 from './Mat33';
|
5
|
+
import Rect2 from './Rect2';
|
6
|
+
import { Point2 } from './Vec2';
|
7
|
+
export declare enum PathCommandType {
|
8
|
+
LineTo = 0,
|
9
|
+
MoveTo = 1,
|
10
|
+
CubicBezierTo = 2,
|
11
|
+
QuadraticBezierTo = 3
|
12
|
+
}
|
13
|
+
export interface CubicBezierPathCommand {
|
14
|
+
kind: PathCommandType.CubicBezierTo;
|
15
|
+
controlPoint1: Point2;
|
16
|
+
controlPoint2: Point2;
|
17
|
+
endPoint: Point2;
|
18
|
+
}
|
19
|
+
export interface QuadraticBezierPathCommand {
|
20
|
+
kind: PathCommandType.QuadraticBezierTo;
|
21
|
+
controlPoint: Point2;
|
22
|
+
endPoint: Point2;
|
23
|
+
}
|
24
|
+
export interface LinePathCommand {
|
25
|
+
kind: PathCommandType.LineTo;
|
26
|
+
point: Point2;
|
27
|
+
}
|
28
|
+
export interface MoveToPathCommand {
|
29
|
+
kind: PathCommandType.MoveTo;
|
30
|
+
point: Point2;
|
31
|
+
}
|
32
|
+
export declare type PathCommand = CubicBezierPathCommand | LinePathCommand | QuadraticBezierPathCommand | MoveToPathCommand;
|
33
|
+
interface IntersectionResult {
|
34
|
+
curve: LineSegment2 | Bezier;
|
35
|
+
parameterValue: number;
|
36
|
+
point: Point2;
|
37
|
+
}
|
38
|
+
export default class Path {
|
39
|
+
readonly startPoint: Point2;
|
40
|
+
readonly parts: PathCommand[];
|
41
|
+
private cachedGeometry;
|
42
|
+
readonly bbox: Rect2;
|
43
|
+
constructor(startPoint: Point2, parts: PathCommand[]);
|
44
|
+
get geometry(): Array<LineSegment2 | Bezier>;
|
45
|
+
static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
|
46
|
+
intersection(line: LineSegment2): IntersectionResult[];
|
47
|
+
transformedBy(affineTransfm: Mat33): Path;
|
48
|
+
union(other: Path | null): Path;
|
49
|
+
static fromRenderable(renderable: RenderablePathSpec): Path;
|
50
|
+
toRenderable(fill: RenderingStyle): RenderablePathSpec;
|
51
|
+
toString(): string;
|
52
|
+
static toString(startPoint: Point2, parts: PathCommand[]): string;
|
53
|
+
static fromString(pathString: string): Path;
|
54
|
+
}
|
55
|
+
export {};
|