tela.js 1.1.8 → 1.1.9
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/package.json +1 -1
- package/src/Camera/parallel.js +1 -2
- package/src/Camera/rayTraceWorker.js +1 -2
- package/src/IO/IO.js +3 -3
- package/src/Tela/Canvas.js +4 -4
- package/src/Tela/Image.js +27 -9
- package/src/Tela/Tela.js +14 -2
- package/src/Tela/Window.js +7 -2
- package/src/Utils/Constants.js +1 -0
- package/src/Utils/Math.js +17 -0
- package/src/Utils/SVG.js +553 -40
- package/src/Utils/Utils.js +9 -8
- package/src/index.js +2 -1
package/package.json
CHANGED
package/src/Camera/parallel.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { IS_NODE, NUMBER_OF_CORES } from "../Utils/Constants.js";
|
|
1
|
+
import { CHANNELS, IS_NODE, NUMBER_OF_CORES } from "../Utils/Constants.js";
|
|
2
2
|
import Color from "../Color/Color.js";
|
|
3
|
-
import { CHANNELS } from "../Tela/Tela.js";
|
|
4
3
|
|
|
5
4
|
//========================================================================================
|
|
6
5
|
/* *
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Camera from "./Camera.js";
|
|
2
2
|
import { rayTrace } from "./rayTrace.js";
|
|
3
|
-
import { CHANNELS } from "../
|
|
4
|
-
import { IS_NODE } from "../Utils/Constants.js";
|
|
3
|
+
import { CHANNELS, IS_NODE } from "../Utils/Constants.js";
|
|
5
4
|
import { deserializeScene } from "../Scene/utils.js";
|
|
6
5
|
import Color from "../Color/Color.js";
|
|
7
6
|
|
package/src/IO/IO.js
CHANGED
|
@@ -7,7 +7,7 @@ export function saveImageToFile(fileAddress, image) {
|
|
|
7
7
|
const ppmName = `${fileName}.ppm`;
|
|
8
8
|
writeFileSync(ppmName, createPPMFromImage(image));
|
|
9
9
|
if (extension !== "ppm") {
|
|
10
|
-
execSync(`ffmpeg -i ${ppmName} ${fileName}.${extension}`);
|
|
10
|
+
execSync(`ffmpeg -i ${ppmName} -y ${fileName}.${extension}`);
|
|
11
11
|
unlinkSync(ppmName)
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -89,7 +89,7 @@ export function saveImageStreamToVideo(fileAddress, streamWithImages, { imageGet
|
|
|
89
89
|
s = await s.tail;
|
|
90
90
|
}
|
|
91
91
|
if (!fps) fps = ite / time;
|
|
92
|
-
execSync(`ffmpeg -framerate ${fps} -i ${fileName}_%d.ppm ${fileName}.${extension}`);
|
|
92
|
+
execSync(`ffmpeg -framerate ${fps} -i ${fileName}_%d.ppm -y ${fileName}.${extension}`);
|
|
93
93
|
for (let i = 0; i < ite; i++) {
|
|
94
94
|
unlinkSync(`${fileName}_${i}.ppm`);
|
|
95
95
|
}
|
|
@@ -106,8 +106,8 @@ export function saveParallelImageStreamToVideo(fileAddress, parallelStreamOfImag
|
|
|
106
106
|
const promises = inputParamsPartitions.map((inputParams, i) => {
|
|
107
107
|
const spawnFile = "IO_parallel" + i + ".js";
|
|
108
108
|
writeFileSync(spawnFile, `
|
|
109
|
-
import * as _module from "./src/index.node.js"
|
|
110
109
|
import fs from "node:fs";
|
|
110
|
+
import * as _module from "./src/index.node.js"
|
|
111
111
|
const {
|
|
112
112
|
Box,
|
|
113
113
|
Vec,
|
package/src/Tela/Canvas.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Color from "../Color/Color.js";
|
|
2
|
-
import { MAX_8BIT } from "../Utils/Constants.js";
|
|
2
|
+
import { CHANNELS, MAX_8BIT } from "../Utils/Constants.js";
|
|
3
3
|
import { memoize } from "../Utils/Utils.js";
|
|
4
|
-
import Tela
|
|
4
|
+
import Tela from "./Tela.js";
|
|
5
5
|
|
|
6
6
|
export default class Canvas extends Tela {
|
|
7
7
|
|
|
@@ -228,10 +228,10 @@ const createWorker = (main, lambda, dependencies) => {
|
|
|
228
228
|
const lambda = ${lambda.toString()};
|
|
229
229
|
const __main__ = ${main.toString()};
|
|
230
230
|
onmessage = e => {
|
|
231
|
-
const input = e.data
|
|
231
|
+
const input = e.data;
|
|
232
232
|
const output = __main__(input);
|
|
233
233
|
self.postMessage(output);
|
|
234
234
|
};
|
|
235
235
|
`;
|
|
236
|
-
return new Worker(URL.createObjectURL(new Blob([workerFile])));
|
|
236
|
+
return new Worker(URL.createObjectURL(new Blob([workerFile], { type: 'application/javascript' })), { type: "module" });
|
|
237
237
|
};
|
package/src/Tela/Image.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import Tela from "./Tela.js";
|
|
1
2
|
import Color from "../Color/Color.js";
|
|
2
3
|
import { readImageFrom } from "../IO/IO.js";
|
|
3
4
|
import { memoize } from "../Utils/Utils.js";
|
|
4
|
-
import
|
|
5
|
+
import { CHANNELS } from "../Utils/Constants.js";
|
|
5
6
|
import { Worker } from "node:worker_threads";
|
|
6
7
|
import os from "node:os";
|
|
7
8
|
|
|
@@ -116,14 +117,31 @@ export default class Image extends Tela {
|
|
|
116
117
|
const createWorker = (main, lambda, dependencies) => {
|
|
117
118
|
const workerFile = `
|
|
118
119
|
const { parentPort } = require("node:worker_threads");
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
import("./src/index.js").then(_module => {
|
|
121
|
+
const {
|
|
122
|
+
Box,
|
|
123
|
+
Vec,
|
|
124
|
+
Vec2,
|
|
125
|
+
Vec3,
|
|
126
|
+
Mesh,
|
|
127
|
+
Color,
|
|
128
|
+
Image,
|
|
129
|
+
BScene,
|
|
130
|
+
Camera,
|
|
131
|
+
KScene,
|
|
132
|
+
Sphere,
|
|
133
|
+
CHANNELS
|
|
134
|
+
MAX_8BIT,
|
|
135
|
+
NaiveScene,
|
|
136
|
+
} = _module;
|
|
137
|
+
${dependencies.map(d => d.toString()).join("\n")}
|
|
138
|
+
const lambda = ${lambda.toString()};
|
|
139
|
+
const __main__ = ${main.toString()};
|
|
140
|
+
parentPort.on("message", message => {
|
|
141
|
+
const output = __main__(message);
|
|
142
|
+
parentPort.postMessage(output);
|
|
143
|
+
});
|
|
144
|
+
})
|
|
127
145
|
`;
|
|
128
146
|
const worker = new Worker(workerFile, { eval: true });
|
|
129
147
|
return worker;
|
package/src/Tela/Tela.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import Color from "../Color/Color.js";
|
|
2
2
|
import Box from "../Geometry/Box.js";
|
|
3
|
+
import { CHANNELS } from "../Utils/Constants.js";
|
|
3
4
|
import { mod } from "../Utils/Math.js";
|
|
4
5
|
import { Vec2 } from "../Vector/Vector.js";
|
|
5
6
|
|
|
6
|
-
export const CHANNELS = 4;
|
|
7
|
-
|
|
8
7
|
// Abstract Image
|
|
9
8
|
export default class Tela {
|
|
10
9
|
constructor(width, height) {
|
|
@@ -42,6 +41,19 @@ export default class Tela {
|
|
|
42
41
|
return this.paint();
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
mapBox = (lambda, box) => {
|
|
45
|
+
const init = box.min;
|
|
46
|
+
const end = box.max;
|
|
47
|
+
for (let x = init.x; x < end.x; x++) {
|
|
48
|
+
for (let y = init.y; y < end.y; y++) {
|
|
49
|
+
const color = lambda(x - init.x, y - init.y);
|
|
50
|
+
if (!color) continue;
|
|
51
|
+
this.setPxl(x, y, color);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
fill(color) {
|
|
46
58
|
if (!color) return;
|
|
47
59
|
const n = this.image.length;
|
package/src/Tela/Window.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { memoize } from "../Utils/Utils.js";
|
|
2
2
|
import Color from "../Color/Color.js";
|
|
3
|
-
import { MAX_8BIT } from "../Utils/Constants.js";
|
|
3
|
+
import { CHANNELS, MAX_8BIT } from "../Utils/Constants.js";
|
|
4
4
|
import { clamp } from "../Utils/Math.js";
|
|
5
5
|
import sdl from "@kmamal/sdl";
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import { Worker } from "node:worker_threads";
|
|
8
|
-
import Tela
|
|
8
|
+
import Tela from "./Tela.js";
|
|
9
9
|
import { Buffer } from "node:buffer";
|
|
10
10
|
|
|
11
11
|
|
|
@@ -127,6 +127,11 @@ export default class Window extends Tela {
|
|
|
127
127
|
return this;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
onResizeWindow(lambda) {
|
|
131
|
+
this.window.on("resize", (e) => lambda(e));
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
setWindowSize(w, h) {
|
|
131
136
|
this.window.setSize(w, h);
|
|
132
137
|
return this;
|
package/src/Utils/Constants.js
CHANGED
package/src/Utils/Math.js
CHANGED
|
@@ -19,6 +19,23 @@ export function lerp(a, b) {
|
|
|
19
19
|
return t => a.scale(1 - t).add(b.scale(t));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function qBezier(p1, p2, p3) {
|
|
23
|
+
const q1 = lerp(p1, p2);
|
|
24
|
+
const q2 = lerp(p2, p3);
|
|
25
|
+
return t => lerp(q1(t), q2(t))(t);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function cBezier(p1, p2, p3, p4) {
|
|
29
|
+
const b1 = lerp(p1, p2);
|
|
30
|
+
const b2 = lerp(p2, p3);
|
|
31
|
+
const b3 = lerp(p3, p4);
|
|
32
|
+
return t => {
|
|
33
|
+
const c1 = lerp(b1(t), b2(t));
|
|
34
|
+
const c2 = lerp(b2(t), b3(t));
|
|
35
|
+
return lerp(c1(t), c2(t))(t);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
export function randomPointInSphere(dim) {
|
|
23
40
|
let randomInSphere;
|
|
24
41
|
while (true) {
|
package/src/Utils/SVG.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
SVGTypes -> SVG / Value
|
|
5
|
-
Value -> AnyBut(<)
|
|
6
|
-
StartTag -> < (" ")* AlphaNumName (" " || "\n")* Attrs (" " || "\n")*>
|
|
7
|
-
EmptyTag -> <(" ")* AlphaNumName (" " || "\n")* Attrs (" " || "\n")* />
|
|
8
|
-
Attrs -> Attr (" " || "\n")* Attrs / ε
|
|
9
|
-
Attr -> AlphaNumName="AnyBut(")" / AlphaNumName='AnyBut(')'
|
|
10
|
-
EndTag -> </(" ")*AlphaNumName(" ")*>
|
|
11
|
-
AlphaNumName -> [a-zA-z][a-zA-Z0-9]*
|
|
12
|
-
*/
|
|
1
|
+
import { Vec2 } from "../Vector/Vector.js";
|
|
2
|
+
import { cBezier, qBezier } from "./Math.js";
|
|
3
|
+
|
|
13
4
|
export default function parse(text) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
return SVG;
|
|
5
|
+
const tokensStream = tokens(stream(text));
|
|
6
|
+
const { left: SVG } = parseSVG(eatAllSpacesChars(tokensStream));
|
|
7
|
+
return readSVGNode(SVG);
|
|
17
8
|
}
|
|
18
9
|
|
|
10
|
+
|
|
19
11
|
//========================================================================================
|
|
20
12
|
/* *
|
|
21
13
|
* TOKENIZER *
|
|
@@ -25,8 +17,11 @@ const TOKEN_SYMBOLS = [
|
|
|
25
17
|
"<!--",
|
|
26
18
|
"-->",
|
|
27
19
|
"\n",
|
|
20
|
+
"\r",
|
|
28
21
|
"\t",
|
|
29
22
|
" ",
|
|
23
|
+
"<?",
|
|
24
|
+
"?>",
|
|
30
25
|
"</",
|
|
31
26
|
"/>",
|
|
32
27
|
"<",
|
|
@@ -97,13 +92,26 @@ function symbolParser(symbol) {
|
|
|
97
92
|
* */
|
|
98
93
|
//========================================================================================
|
|
99
94
|
|
|
100
|
-
|
|
95
|
+
/*
|
|
96
|
+
SVG -> StartTag InnerSVG EndTag / EmptyTag / CommentTag (" " || "\n")* SVG / XMLTag (" " || "\n")* SVG
|
|
97
|
+
InnerSVG -> SVGTypes InnerSVG / ε
|
|
98
|
+
SVGTypes -> SVG / CommentTag / Value
|
|
99
|
+
Value -> AnyBut(<)
|
|
100
|
+
StartTag -> < (" ")* AlphaNumName (" " || "\n")* Attrs (" " || "\n")*>
|
|
101
|
+
EmptyTag -> <(" ")* AlphaNumName (" " || "\n")* Attrs (" " || "\n")* />
|
|
102
|
+
Attrs -> Attr (" " || "\n")* Attrs / ε
|
|
103
|
+
Attr -> AlphaNumName="AnyBut(")" / AlphaNumName='AnyBut(')'
|
|
104
|
+
EndTag -> </(" ")*AlphaNumName(" ")*>
|
|
105
|
+
AlphaNumName -> [a-zA-z][a-zA-Z0-9]*
|
|
106
|
+
CommentTag -> <!--AnyBut("-->")-->
|
|
107
|
+
XMLTag -> <?AnyBut("?>")?>
|
|
108
|
+
*/
|
|
101
109
|
function parseSVG(stream) {
|
|
102
110
|
return or(
|
|
103
111
|
() => {
|
|
104
112
|
const { left: StartTag, right: nextStream1 } = parseStartTag(stream);
|
|
105
113
|
const { left: InnerSVG, right: nextStream2 } = parseInnerSVG(nextStream1);
|
|
106
|
-
const { left: EndTag, right: nextStream3 } = parseEndTag(
|
|
114
|
+
const { left: EndTag, right: nextStream3 } = parseEndTag(eatAllSpacesChars(nextStream2));
|
|
107
115
|
return pair({ type: "svg", StartTag, InnerSVG, EndTag }, nextStream3);
|
|
108
116
|
},
|
|
109
117
|
() => {
|
|
@@ -111,24 +119,35 @@ function parseSVG(stream) {
|
|
|
111
119
|
return pair({ type: "svg", EmptyTag }, nextStream);
|
|
112
120
|
},
|
|
113
121
|
() => {
|
|
114
|
-
const {
|
|
115
|
-
|
|
122
|
+
const { right: nextStream } = parseCommentTag(stream);
|
|
123
|
+
const { left: SVG, right: nextStream1 } = parseSVG(eatAllSpacesChars(nextStream));
|
|
124
|
+
return pair({ type: "svg", ...SVG }, nextStream1);
|
|
125
|
+
},
|
|
126
|
+
() => {
|
|
127
|
+
const { right: nextStream } = parseXMLTag(stream);
|
|
128
|
+
const { left: SVG, right: nextStream1 } = parseSVG(eatAllSpacesChars(nextStream));
|
|
129
|
+
return pair({ type: "svg", ...SVG }, nextStream1);
|
|
116
130
|
}
|
|
117
131
|
);
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
function parseValue(stream) {
|
|
121
|
-
const { left: AnyBut, right: nextStream } = parseAnyBut(t => t.type === "<" || t.type === "</")(
|
|
135
|
+
const { left: AnyBut, right: nextStream } = parseAnyBut(t => t.type === "<" || t.type === "</")(eatAllSpacesChars(stream));
|
|
122
136
|
return pair({ type: "value", text: AnyBut.text }, nextStream);
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
function parseSVGTypes(stream) {
|
|
126
140
|
return or(
|
|
127
141
|
() => {
|
|
128
|
-
const cleanStream =
|
|
142
|
+
const cleanStream = eatAllSpacesChars(stream);
|
|
129
143
|
const { left: SVG, right: nextStream } = parseSVG(cleanStream);
|
|
130
144
|
return pair({ type: "svgTypes", SVG }, nextStream);
|
|
131
145
|
},
|
|
146
|
+
() => {
|
|
147
|
+
const cleanStream = eatAllSpacesChars(stream);
|
|
148
|
+
const { left: CommentTag, right: nextStream } = parseCommentTag(cleanStream);
|
|
149
|
+
return pair({ type: "svgTypes", CommentTag }, nextStream);
|
|
150
|
+
},
|
|
132
151
|
() => {
|
|
133
152
|
const { left: Value, right: nextStream } = parseValue(stream);
|
|
134
153
|
if (Value.text === "") throw Error("Fail to parse SVGType")
|
|
@@ -172,7 +191,7 @@ function parseAnyBut(tokenPredicate) {
|
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
function parseEndTag(stream) {
|
|
175
|
-
const filteredStream =
|
|
194
|
+
const filteredStream = eatAllSpacesChars(stream);
|
|
176
195
|
const token = filteredStream.head();
|
|
177
196
|
if ("</" === token.type) {
|
|
178
197
|
const nextStream1 = eatSpaces(filteredStream.tail());
|
|
@@ -190,9 +209,9 @@ function parseEmptyTag(stream) {
|
|
|
190
209
|
if ("<" === token.type) {
|
|
191
210
|
const nextStream1 = eatSpaces(stream.tail());
|
|
192
211
|
const { left: tagName, right: nextStream2 } = parseAlphaNumName(nextStream1)
|
|
193
|
-
const nextStream3 =
|
|
212
|
+
const nextStream3 = eatAllSpacesChars(nextStream2);
|
|
194
213
|
const { left: Attrs, right: nextStream4 } = parseAttrs(nextStream3);
|
|
195
|
-
const nextStream5 =
|
|
214
|
+
const nextStream5 = eatAllSpacesChars(nextStream4);
|
|
196
215
|
if ("/>" === nextStream5.head().type) {
|
|
197
216
|
return pair({ type: "emptyTag", tag: tagName.text, Attrs }, nextStream5.tail());
|
|
198
217
|
}
|
|
@@ -209,14 +228,23 @@ function parseCommentTag(stream) {
|
|
|
209
228
|
throw new Error("Fail to parse CommentTag")
|
|
210
229
|
}
|
|
211
230
|
|
|
231
|
+
function parseXMLTag(stream) {
|
|
232
|
+
if ("<?" === stream.head().type) {
|
|
233
|
+
const nextStream = stream.tail();
|
|
234
|
+
const { right: nextStream1 } = parseAnyBut(token => '?>' === token.type)(nextStream);
|
|
235
|
+
return pair({ type: "xmlTag" }, eatAllSpacesChars(nextStream1.tail()));
|
|
236
|
+
}
|
|
237
|
+
throw new Error("Fail to parse XMLTag")
|
|
238
|
+
}
|
|
239
|
+
|
|
212
240
|
function parseStartTag(stream) {
|
|
213
241
|
const token = stream.head();
|
|
214
242
|
if ("<" === token.type) {
|
|
215
243
|
const nextStream1 = eatSpaces(stream.tail());
|
|
216
244
|
const { left: tagName, right: nextStream2 } = parseAlphaNumName(nextStream1)
|
|
217
|
-
const nextStream3 =
|
|
245
|
+
const nextStream3 = eatAllSpacesChars(nextStream2);
|
|
218
246
|
const { left: Attrs, right: nextStream4 } = parseAttrs(nextStream3);
|
|
219
|
-
const nextStream5 =
|
|
247
|
+
const nextStream5 = eatAllSpacesChars(nextStream4);
|
|
220
248
|
if (">" === nextStream5.head().type) {
|
|
221
249
|
return pair({ type: "startTag", tag: tagName.text, Attrs }, nextStream5.tail());
|
|
222
250
|
}
|
|
@@ -271,7 +299,7 @@ function parseAttrs(stream) {
|
|
|
271
299
|
return or(
|
|
272
300
|
() => {
|
|
273
301
|
const { left: Attr, right: nextStream } = parseAttr(stream);
|
|
274
|
-
const nextStreamNoSpaces =
|
|
302
|
+
const nextStreamNoSpaces = eatAllSpacesChars(nextStream);
|
|
275
303
|
const { left: Attrs, right: nextStream1 } = parseAttrs(nextStreamNoSpaces);
|
|
276
304
|
return pair({
|
|
277
305
|
type: "attrs",
|
|
@@ -287,24 +315,496 @@ function parseAttrs(stream) {
|
|
|
287
315
|
)
|
|
288
316
|
}
|
|
289
317
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
318
|
+
const eatSpaces = eatWhile(p => p.type === " ");
|
|
319
|
+
const eatAllSpacesChars = eatWhile(p => p.type === " " || p.type === "\t" || p.type === "\r" || p.type === "\n")
|
|
320
|
+
|
|
321
|
+
//========================================================================================
|
|
322
|
+
/* *
|
|
323
|
+
* PARSE SVG PATH *
|
|
324
|
+
* */
|
|
325
|
+
//========================================================================================
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
*
|
|
330
|
+
* path -> action path/ ε
|
|
331
|
+
* action -> (" "* | "\n"* )letter(" "* | "\n"*)numbers / letter
|
|
332
|
+
* letter -> [a...zA...Z]
|
|
333
|
+
* numbers -> number(" "* | "\n"* | ",")numbers / ε
|
|
334
|
+
* number -> -D.D / D.D / -D / D
|
|
335
|
+
* D -> [0-9]D / ε
|
|
336
|
+
*/
|
|
337
|
+
export function parseSvgPath(svgPath) {
|
|
338
|
+
const { left: path, } = parsePath(stream(svgPath));
|
|
339
|
+
return path;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function parsePath(iStream) {
|
|
343
|
+
return or(
|
|
344
|
+
() => {
|
|
345
|
+
const { left: action, right: nStream } = parseAction(iStream);
|
|
346
|
+
const { left: path, right: nStream2 } = parsePath(nStream);
|
|
347
|
+
return pair({ type: "path", actions: [{ ...action }, ...path.actions] }, nStream2);
|
|
348
|
+
},
|
|
349
|
+
() => {
|
|
350
|
+
return pair({ type: "path", actions: [] }, iStream)
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function parseAction(iStream) {
|
|
356
|
+
const nStream0 = eatWhile(p => p === " " || p === "\n")(iStream);
|
|
357
|
+
const { left: letter, right: nStream } = parseLetter(nStream0);
|
|
358
|
+
const nStream1 = eatWhile(p => p === " " || p === "\n")(nStream);
|
|
359
|
+
const { left: numbers, right: nStream2 } = parseNumbers(nStream1);
|
|
360
|
+
return pair({ type: "action", letter: letter.letter, numbers: numbers.numbers }, nStream2);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
function parseLetter(iStream) {
|
|
365
|
+
const char = iStream.head();
|
|
366
|
+
if (/^[a-zA-Z]$/.test(char)) {
|
|
367
|
+
return pair({ type: "letter", letter: char }, iStream.tail())
|
|
295
368
|
}
|
|
296
|
-
|
|
369
|
+
throw new Error("Caught exception in parsing letter");
|
|
297
370
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
371
|
+
|
|
372
|
+
function parseNumbers(iStream) {
|
|
373
|
+
return or(
|
|
374
|
+
() => {
|
|
375
|
+
const { left: number, right: nStream } = parseNumber(iStream);
|
|
376
|
+
const nStream2 = eatWhile(p => p === " " || p === "\n" || p === ",")(nStream);
|
|
377
|
+
const { left: numbers, right: nStream3 } = parseNumbers(nStream2);
|
|
378
|
+
return pair({ type: "numbers", numbers: [number.number, ...numbers.numbers] }, nStream3);
|
|
379
|
+
},
|
|
380
|
+
() => {
|
|
381
|
+
return pair({ type: "numbers", numbers: [] }, iStream);
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function parseNumber(iStream) {
|
|
388
|
+
return or(
|
|
389
|
+
() => {
|
|
390
|
+
let nStream = iStream;
|
|
391
|
+
if (nStream.head() === "-") {
|
|
392
|
+
nStream = nStream.tail();
|
|
393
|
+
const { left: D, right: nStream2 } = parseD(nStream);
|
|
394
|
+
if (nStream2.head() === ".") {
|
|
395
|
+
const { left: D2, right: nStream3 } = parseD(nStream2.tail()); // remove .
|
|
396
|
+
return pair({ type: "number", number: Number.parseFloat(`-${D.d}.${D2.d}`) }, nStream3);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
throw new Error("fail to parse -D.D");
|
|
400
|
+
},
|
|
401
|
+
() => {
|
|
402
|
+
const { left: D, right: nStream } = parseD(iStream);
|
|
403
|
+
if (nStream.head() === ".") {
|
|
404
|
+
const { left: D2, right: nStream2 } = parseD(nStream.tail()); // remove .
|
|
405
|
+
return pair({ type: "number", number: Number.parseFloat(`${D.d}.${D2.d}`) }, nStream2);
|
|
406
|
+
}
|
|
407
|
+
throw new Error("fail to parse D.D");
|
|
408
|
+
},
|
|
409
|
+
() => {
|
|
410
|
+
let nStream = iStream;
|
|
411
|
+
if (nStream.head() === "-") {
|
|
412
|
+
nStream = nStream.tail();
|
|
413
|
+
const { left: D, right: nStream2 } = parseD(nStream);
|
|
414
|
+
return pair({ type: "number", number: Number.parseFloat(`-${D.d}`) }, nStream2);
|
|
415
|
+
}
|
|
416
|
+
throw new Error("fail to parse -D");
|
|
417
|
+
},
|
|
418
|
+
() => {
|
|
419
|
+
const { left: D, right: nStream } = parseD(iStream);
|
|
420
|
+
if (D.d === "") throw new Error("fail to parse D");
|
|
421
|
+
return pair({ type: "number", number: Number.parseFloat(`${D.d}`) }, nStream)
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function parseD(iStream) {
|
|
427
|
+
return or(
|
|
428
|
+
() => {
|
|
429
|
+
const char = iStream.head();
|
|
430
|
+
if (/^[0-9]$/.test(char)) {
|
|
431
|
+
const nStream = iStream.tail();
|
|
432
|
+
const { left: D, right: nStream2 } = parseD(nStream);
|
|
433
|
+
return pair({ type: "D", d: `${char}${D.d}` }, nStream2);
|
|
434
|
+
}
|
|
435
|
+
throw new Error("Caught exception in parsing D");
|
|
436
|
+
},
|
|
437
|
+
() => {
|
|
438
|
+
return pair({ type: "D", d: "" }, iStream);
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function eatWhile(predicate) {
|
|
445
|
+
return iStream => {
|
|
446
|
+
let s = iStream;
|
|
447
|
+
while (!s.isEmpty() && predicate(s.head())) {
|
|
448
|
+
s = s.tail();
|
|
449
|
+
}
|
|
450
|
+
return s;
|
|
304
451
|
}
|
|
305
|
-
return s;
|
|
306
452
|
}
|
|
307
453
|
|
|
454
|
+
//========================================================================================
|
|
455
|
+
/* *
|
|
456
|
+
* SVG READER *
|
|
457
|
+
* */
|
|
458
|
+
//========================================================================================
|
|
459
|
+
|
|
460
|
+
function addFirstPointIfNeeded(currentPos, path, keyPointPath) {
|
|
461
|
+
if (keyPointPath.length === 0) {
|
|
462
|
+
path.push(currentPos);
|
|
463
|
+
keyPointPath.push(currentPos);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function readPath(svg, tagNode) {
|
|
468
|
+
let path = [];
|
|
469
|
+
let keyPointPath = [];
|
|
470
|
+
const samples = 10;
|
|
471
|
+
let currentPos = Vec2();
|
|
472
|
+
const [svgPath] = tagNode.Attrs.attributes.filter(a => a.attributeName === "d");
|
|
473
|
+
const [idObj] = tagNode.Attrs.attributes.filter(a => a.attributeName === "id");
|
|
474
|
+
const id = idObj?.attributeValue ?? generateUniqueID(5);
|
|
475
|
+
const letter2action = {
|
|
476
|
+
"M": (vecs) => {
|
|
477
|
+
const [p] = vecs;
|
|
478
|
+
currentPos = p;
|
|
479
|
+
},
|
|
480
|
+
"m": (vecs) => {
|
|
481
|
+
const [p] = vecs;
|
|
482
|
+
currentPos = currentPos.add(p);
|
|
483
|
+
},
|
|
484
|
+
"L": (vecs) => {
|
|
485
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
486
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
487
|
+
path.push(vecs[j]);
|
|
488
|
+
keyPointPath.push(vecs[j]);
|
|
489
|
+
currentPos = path.at(-1);
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
"l": (vecs) => {
|
|
493
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
494
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
495
|
+
path.push(currentPos.add(vecs[j]));
|
|
496
|
+
keyPointPath.push(currentPos.add(vecs[j]));
|
|
497
|
+
currentPos = path.at(-1);
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"V": (vecs) => {
|
|
501
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
502
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
503
|
+
const newP = Vec2(currentPos.x, vecs[j].y);
|
|
504
|
+
path.push(newP);
|
|
505
|
+
keyPointPath.push(newP);
|
|
506
|
+
currentPos = path.at(-1);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
"v": (vecs) => {
|
|
510
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
511
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
512
|
+
path.push(currentPos.add(vecs[j]));
|
|
513
|
+
keyPointPath.push(currentPos.add(vecs[j]));
|
|
514
|
+
currentPos = path.at(-1);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
"H": (vecs) => {
|
|
518
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
519
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
520
|
+
const newP = Vec2(vecs[j].x, currentPos.y);
|
|
521
|
+
path.push(newP);
|
|
522
|
+
keyPointPath.push(newP);
|
|
523
|
+
currentPos = path.at(-1);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
"h": (vecs) => {
|
|
527
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
528
|
+
for (let j = 0; j < vecs.length; j += 1) {
|
|
529
|
+
path.push(currentPos.add(vecs[j]));
|
|
530
|
+
keyPointPath.push(currentPos.add(vecs[j]));
|
|
531
|
+
currentPos = path.at(-1);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
"Q": (vecs) => {
|
|
535
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
536
|
+
for (let j = 0; j < vecs.length; j += 2) {
|
|
537
|
+
const qb = qBezier(currentPos, vecs[j], vecs[j + 1]);
|
|
538
|
+
for (let i = 0; i < samples; i++) {
|
|
539
|
+
path.push(qb(i / (samples - 1)));
|
|
540
|
+
}
|
|
541
|
+
keyPointPath.push(currentPos, vecs[j], vecs[j + 1]);
|
|
542
|
+
currentPos = path.at(-1);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
"q": (vecs) => {
|
|
546
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
547
|
+
for (let j = 0; j < vecs.length; j += 2) {
|
|
548
|
+
const qb = qBezier(currentPos, currentPos.add(vecs[j]), currentPos.add(vecs[j + 1]));
|
|
549
|
+
for (let i = 0; i < samples; i++) {
|
|
550
|
+
path.push(qb(i / (samples - 1)));
|
|
551
|
+
}
|
|
552
|
+
keyPointPath.push(currentPos, currentPos.add(vecs[j]), currentPos.add(vecs[j + 1]));
|
|
553
|
+
currentPos = path.at(-1);
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
"T": (vecs) => {
|
|
557
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
558
|
+
const [end] = vecs;
|
|
559
|
+
const prevKeyPoint = keyPointPath.at(-2) ? keyPointPath.at(-2) : keyPointPath.at(-1);
|
|
560
|
+
const control = currentPos.scale(2).sub(prevKeyPoint); // reflection bla bla
|
|
561
|
+
const qb = qBezier(currentPos, control, end);
|
|
562
|
+
for (let i = 0; i < samples; i++) {
|
|
563
|
+
path.push(qb(i / (samples - 1)));
|
|
564
|
+
}
|
|
565
|
+
keyPointPath.push(
|
|
566
|
+
currentPos,
|
|
567
|
+
control,
|
|
568
|
+
end
|
|
569
|
+
)
|
|
570
|
+
currentPos = path.at(-1);
|
|
571
|
+
},
|
|
572
|
+
"t": (vecs) => {
|
|
573
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
574
|
+
const [end] = vecs;
|
|
575
|
+
const prevKeyPoint = keyPointPath.at(-2) ? keyPointPath.at(-2) : keyPointPath.at(-1);
|
|
576
|
+
const control = currentPos.scale(2).sub(prevKeyPoint); // reflection bla bla
|
|
577
|
+
const qb = qBezier(currentPos, control, currentPos.add(end));
|
|
578
|
+
for (let i = 0; i < samples; i++) {
|
|
579
|
+
path.push(qb(i / (samples - 1)));
|
|
580
|
+
}
|
|
581
|
+
keyPointPath.push(
|
|
582
|
+
currentPos,
|
|
583
|
+
control,
|
|
584
|
+
currentPos.add(end)
|
|
585
|
+
)
|
|
586
|
+
currentPos = path.at(-1);
|
|
587
|
+
},
|
|
588
|
+
"C": (vecs) => {
|
|
589
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
590
|
+
for (let j = 0; j < vecs.length; j += 3) {
|
|
591
|
+
const cb = cBezier(
|
|
592
|
+
currentPos,
|
|
593
|
+
vecs[j],
|
|
594
|
+
vecs[j + 1],
|
|
595
|
+
vecs[j + 2]
|
|
596
|
+
);
|
|
597
|
+
for (let i = 0; i < samples; i++) {
|
|
598
|
+
path.push(cb(i / (samples - 1)));
|
|
599
|
+
}
|
|
600
|
+
keyPointPath.push(
|
|
601
|
+
currentPos,
|
|
602
|
+
vecs[j],
|
|
603
|
+
vecs[j + 1],
|
|
604
|
+
vecs[j + 2]
|
|
605
|
+
)
|
|
606
|
+
currentPos = path.at(-1);
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"c": (vecs) => {
|
|
610
|
+
addFirstPointIfNeeded(currentPos, path, keyPointPath);
|
|
611
|
+
for (let j = 0; j < vecs.length; j += 3) {
|
|
612
|
+
const cb = cBezier(
|
|
613
|
+
currentPos,
|
|
614
|
+
currentPos.add(vecs[j]),
|
|
615
|
+
currentPos.add(vecs[j + 1]),
|
|
616
|
+
currentPos.add(vecs[j + 2])
|
|
617
|
+
);
|
|
618
|
+
for (let i = 0; i < samples; i++) {
|
|
619
|
+
path.push(cb(i / (samples - 1)));
|
|
620
|
+
}
|
|
621
|
+
keyPointPath.push(
|
|
622
|
+
currentPos,
|
|
623
|
+
currentPos.add(vecs[j]),
|
|
624
|
+
currentPos.add(vecs[j + 1]),
|
|
625
|
+
currentPos.add(vecs[j + 2])
|
|
626
|
+
)
|
|
627
|
+
currentPos = path.at(-1);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
"Z": () => {
|
|
631
|
+
if (keyPointPath.length === 0) return;
|
|
632
|
+
path.push(keyPointPath[0]);
|
|
633
|
+
keyPointPath.push(keyPointPath[0]);
|
|
634
|
+
if (!svg.defPaths[id]) {
|
|
635
|
+
svg.defPaths[id] = [];
|
|
636
|
+
svg.defKeyPointPaths[id] = [];
|
|
637
|
+
}
|
|
638
|
+
svg.defPaths[id].push(path);
|
|
639
|
+
svg.defKeyPointPaths[id].push(keyPointPath);
|
|
640
|
+
path = [];
|
|
641
|
+
keyPointPath = [];
|
|
642
|
+
},
|
|
643
|
+
"z": () => {
|
|
644
|
+
if (keyPointPath.length === 0) return;
|
|
645
|
+
path.push(keyPointPath[0]);
|
|
646
|
+
keyPointPath.push(keyPointPath[0]);
|
|
647
|
+
if (!svg.defPaths[id]) {
|
|
648
|
+
svg.defPaths[id] = [];
|
|
649
|
+
svg.defKeyPointPaths[id] = [];
|
|
650
|
+
}
|
|
651
|
+
svg.defPaths[id].push(path);
|
|
652
|
+
svg.defKeyPointPaths[id].push(keyPointPath);
|
|
653
|
+
path = [];
|
|
654
|
+
keyPointPath = [];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const { actions } = parseSvgPath(svgPath.attributeValue);
|
|
658
|
+
const vectorizedActions = actions
|
|
659
|
+
.map(({ letter, numbers }) => {
|
|
660
|
+
const vectors = [];
|
|
661
|
+
const l = letter.toLowerCase();
|
|
662
|
+
if (l === "v" || l === "h") {
|
|
663
|
+
for (let i = 0; i < numbers.length; i += 1) {
|
|
664
|
+
if (l === "v") vectors.push(Vec2(0, numbers[i]));
|
|
665
|
+
if (l === "h") vectors.push(Vec2(numbers[i], 0));
|
|
666
|
+
}
|
|
667
|
+
} else {
|
|
668
|
+
for (let i = 0; i < numbers.length; i += 2) {
|
|
669
|
+
vectors.push(Vec2(numbers[i], numbers[i + 1]));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return { letter, vectors };
|
|
673
|
+
})
|
|
674
|
+
vectorizedActions.forEach(({ letter, vectors }) => {
|
|
675
|
+
return (letter2action?.[letter] ?? (() => { }))(vectors);
|
|
676
|
+
});
|
|
677
|
+
if (path.length > 0) {
|
|
678
|
+
if (!svg.defPaths[id]) {
|
|
679
|
+
svg.defPaths[id] = [];
|
|
680
|
+
svg.defKeyPointPaths[id] = [];
|
|
681
|
+
}
|
|
682
|
+
svg.defPaths[id].push(path);
|
|
683
|
+
svg.defKeyPointPaths[id].push(keyPointPath);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const transformBuilder = (a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) => x => Vec2(a, b).scale(x.x).add(Vec2(c, d).scale(x.y)).add(Vec2(e, f));
|
|
688
|
+
const dot = (f, g) => x => f(g(x));
|
|
689
|
+
|
|
690
|
+
function readTransform(svg, transformNode, transform = transformBuilder()) {
|
|
691
|
+
(transformNode?.StartTag ?? transformNode?.EmptyTag)
|
|
692
|
+
?.Attrs
|
|
693
|
+
.attributes
|
|
694
|
+
.filter(x => x.attributeName === "transform")
|
|
695
|
+
.forEach(({ attributeValue }) => {
|
|
696
|
+
const params = attributeValue.match(/-?\d+\.?\d*/g).map(Number);
|
|
697
|
+
if (attributeValue.includes("matrix")) {
|
|
698
|
+
transform = dot(transform, transformBuilder(...params));
|
|
699
|
+
}
|
|
700
|
+
if (attributeValue.includes("translate")) {
|
|
701
|
+
transform = dot(transform, transformBuilder(1, 0, 0, 1, ...params))
|
|
702
|
+
}
|
|
703
|
+
if (attributeValue.includes("scale")) {
|
|
704
|
+
transform = dot(transform, transformBuilder(params[0], 0, params[1], 0, 0, 0))
|
|
705
|
+
}
|
|
706
|
+
})
|
|
707
|
+
const nodeStack = [...(transformNode?.InnerSVG?.innerSvgs?.map(x => x.SVG) ?? [])];
|
|
708
|
+
while (nodeStack.length > 0) {
|
|
709
|
+
const currentNode = nodeStack.shift(); // dequeue
|
|
710
|
+
const node = currentNode?.StartTag ?? currentNode?.EmptyTag;
|
|
711
|
+
const tag = node?.tag;
|
|
712
|
+
if (tag === "g") {
|
|
713
|
+
readTransform(svg, currentNode, transform);
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (tag === "use") {
|
|
717
|
+
const useParams = {
|
|
718
|
+
id: undefined,
|
|
719
|
+
transform
|
|
720
|
+
}
|
|
721
|
+
node.Attrs?.attributes.forEach(({ attributeName, attributeValue }) => {
|
|
722
|
+
if (attributeName === "xlink:href") {
|
|
723
|
+
useParams.id = attributeValue.slice(1);
|
|
724
|
+
}
|
|
725
|
+
if (attributeName === "transform") {
|
|
726
|
+
const params = attributeValue.match(/-?\d+\.?\d*/g).map(Number);
|
|
727
|
+
if (attributeValue.includes("scale")) {
|
|
728
|
+
useParams.transform = dot(useParams.transform, transformBuilder(params[0], 0, 0, params[0], 0, 0));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (attributeName === "x") {
|
|
732
|
+
useParams.transform = dot(useParams.transform, transformBuilder(1, 0, 0, 1, Number(attributeValue), 0));
|
|
733
|
+
}
|
|
734
|
+
if (attributeName === "y") {
|
|
735
|
+
useParams.transform = dot(useParams.transform, transformBuilder(1, 0, 0, 1, 0, Number(attributeValue)));
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
if (useParams.id && svg.defPaths[useParams.id]) {
|
|
739
|
+
svg.paths.push(svg.defPaths[useParams.id].map(paths => paths.map(useParams.transform)));
|
|
740
|
+
svg.keyPointPaths.push(svg.defKeyPointPaths[useParams.id].map(paths => paths.map(useParams.transform)));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
nodeStack.push(...(currentNode?.InnerSVG?.innerSvgs?.map(x => x.SVG) ?? []));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function readSVGNode(svgNode) {
|
|
749
|
+
const svg = {
|
|
750
|
+
width: undefined,
|
|
751
|
+
height: undefined,
|
|
752
|
+
viewBox: {},
|
|
753
|
+
defPaths: {},
|
|
754
|
+
defKeyPointPaths: {},
|
|
755
|
+
paths: [],
|
|
756
|
+
keyPointPaths: []
|
|
757
|
+
}
|
|
758
|
+
const nodeStack = [svgNode];
|
|
759
|
+
while (nodeStack.length > 0) {
|
|
760
|
+
const currentNode = nodeStack.shift(); // dequeue
|
|
761
|
+
const tag = currentNode?.StartTag?.tag ?? currentNode?.EmptyTag?.tag;
|
|
762
|
+
if (tag === "svg") {
|
|
763
|
+
currentNode
|
|
764
|
+
.StartTag
|
|
765
|
+
.Attrs
|
|
766
|
+
.attributes
|
|
767
|
+
.forEach(attr => {
|
|
768
|
+
if (attr.attributeName === "viewBox") {
|
|
769
|
+
const vb = attr.attributeValue;
|
|
770
|
+
const [x, y, w, h] = vb.split(" ").map(x => Number.parseFloat(x));
|
|
771
|
+
svg.viewBox.min = Vec2(x, y);
|
|
772
|
+
svg.viewBox.max = Vec2(x + w, y + h);
|
|
773
|
+
}
|
|
774
|
+
if (attr.attributeName === "width") {
|
|
775
|
+
svg.width = Number.parseInt(attr.attributeValue);
|
|
776
|
+
}
|
|
777
|
+
if (attr.attributeName === "height") {
|
|
778
|
+
svg.height = Number.parseInt(attr.attributeValue);
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
if (tag === "defs") {
|
|
783
|
+
const defNodes = (currentNode?.InnerSVG?.innerSvgs?.map(x => x.SVG) ?? []);
|
|
784
|
+
defNodes.forEach(defNode => {
|
|
785
|
+
const defTag = defNode?.StartTag?.tag ?? currentNode?.EmptyTag?.tag;
|
|
786
|
+
if (defTag !== "path") return;
|
|
787
|
+
readPath(svg, defNode.EmptyTag ?? defNode.StartTag);
|
|
788
|
+
})
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (tag === "g") {
|
|
792
|
+
readTransform(svg, currentNode);
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (tag === "path") {
|
|
796
|
+
readPath(svg, currentNode.EmptyTag ?? currentNode.StartTag);
|
|
797
|
+
}
|
|
798
|
+
nodeStack.push(...(currentNode?.InnerSVG?.innerSvgs?.map(x => x.SVG) ?? []));
|
|
799
|
+
}
|
|
800
|
+
if(svg.paths.length === 0) {
|
|
801
|
+
Object.values(svg.defPaths).forEach(paths => svg.paths.push(paths));
|
|
802
|
+
Object.values(svg.defKeyPointPaths).forEach(paths => svg.keyPointPaths.push(paths));
|
|
803
|
+
}
|
|
804
|
+
return svg;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
|
|
308
808
|
//========================================================================================
|
|
309
809
|
/* *
|
|
310
810
|
* UTILS *
|
|
@@ -348,3 +848,16 @@ function stream(stringOrArray) {
|
|
|
348
848
|
}
|
|
349
849
|
};
|
|
350
850
|
}
|
|
851
|
+
|
|
852
|
+
function generateUniqueID(length) {
|
|
853
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
854
|
+
const charactersLength = characters.length;
|
|
855
|
+
|
|
856
|
+
let randomID = '';
|
|
857
|
+
|
|
858
|
+
for (let i = 0; i < length; i++) {
|
|
859
|
+
randomID += characters[Math.floor(Math.random() * charactersLength)];
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return randomID;
|
|
863
|
+
}
|
package/src/Utils/Utils.js
CHANGED
|
@@ -57,26 +57,27 @@ const setTimeOut = typeof window === "undefined" ? setTimeout : requestAnimation
|
|
|
57
57
|
if(typeof window !== "undefined") window.globalAnimationIDs = [];
|
|
58
58
|
export function loop(lambda) {
|
|
59
59
|
let isFinished = false;
|
|
60
|
+
const loopControl = {
|
|
61
|
+
stop: () => {
|
|
62
|
+
isFinished = true;
|
|
63
|
+
},
|
|
64
|
+
play: () => play({ oldT: new Date().getTime(), time: 0 })
|
|
65
|
+
};
|
|
60
66
|
const play = async ({ time, oldT }) => {
|
|
61
67
|
const newT = new Date().getTime();
|
|
62
68
|
const dt = (newT - oldT) * 1e-3;
|
|
63
69
|
|
|
64
70
|
await lambda({ time, dt });
|
|
65
71
|
|
|
66
|
-
if (isFinished) return;
|
|
72
|
+
if (isFinished) return loopControl;
|
|
67
73
|
const id = setTimeOut(() => play({
|
|
68
74
|
oldT: newT,
|
|
69
75
|
time: time + dt,
|
|
70
76
|
}));
|
|
71
77
|
if(typeof window !== "undefined") window.globalAnimationIDs.push(id);
|
|
72
|
-
}
|
|
73
|
-
const loopControl = {
|
|
74
|
-
stop: () => {
|
|
75
|
-
isFinished = true;
|
|
76
|
-
},
|
|
77
|
-
play: () => play({ oldT: new Date().getTime(), time: 0 })
|
|
78
|
-
};
|
|
79
78
|
|
|
79
|
+
return loopControl;
|
|
80
|
+
}
|
|
80
81
|
return loopControl;
|
|
81
82
|
}
|
|
82
83
|
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import Path from "./Geometry/Path.js"
|
|
|
16
16
|
import Triangle from "./Geometry/Triangle.js"
|
|
17
17
|
import Mesh from "./Geometry/Mesh.js"
|
|
18
18
|
import Ray from "./Ray/Ray.js"
|
|
19
|
-
import parseSVG from "./Utils/SVG.js"
|
|
19
|
+
import parseSVG, {parseSvgPath} from "./Utils/SVG.js"
|
|
20
20
|
|
|
21
21
|
export {
|
|
22
22
|
Box,
|
|
@@ -40,6 +40,7 @@ export {
|
|
|
40
40
|
NaiveScene,
|
|
41
41
|
VoxelScene,
|
|
42
42
|
RandomScene,
|
|
43
|
+
parseSvgPath,
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export * from "./Utils/Math.js"
|