@es3d/es3d 1.0.0 → 1.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/LICENSE +201 -201
- package/README.md +86 -86
- package/docs/SPEC.md +417 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +377 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +92 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +579 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +364 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- package/src/wasm/kernels.js +385 -385
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
// Tokenizer for the ES3D compact syntax ("ES3D-C").
|
|
2
|
-
//
|
|
3
|
-
// es3d version=1
|
|
4
|
-
// material#red c=#e53935
|
|
5
|
-
// box s=2 m=red
|
|
6
|
-
// group p=0,2,0 { sphere r=1 }
|
|
7
|
-
// use @tree p=10,0,5
|
|
8
|
-
//
|
|
9
|
-
// Newlines are insignificant: an element header ends where the next token is
|
|
10
|
-
// not an attribute (NAME '='), so the grammar is LL(2) and needs no schema.
|
|
11
|
-
|
|
12
|
-
import { Diagnostic, ES3DError } from './errors.js';
|
|
13
|
-
|
|
14
|
-
export const CTok = Object.freeze({
|
|
15
|
-
NAME: 'name',
|
|
16
|
-
ID: 'id', // #name
|
|
17
|
-
REF: 'ref', // @name
|
|
18
|
-
EQ: 'eq',
|
|
19
|
-
VALUE: 'value', // bare or quoted attribute value
|
|
20
|
-
STRING: 'string', // quoted text content in header position
|
|
21
|
-
LBRACE: 'lbrace',
|
|
22
|
-
RBRACE: 'rbrace',
|
|
23
|
-
SEMI: 'semi',
|
|
24
|
-
COMMENT: 'comment',
|
|
25
|
-
EOF: 'eof',
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
function isSpace(c) {
|
|
29
|
-
return c === 32 || c === 10 || c === 9 || c === 13;
|
|
30
|
-
}
|
|
31
|
-
function isNameStart(c) {
|
|
32
|
-
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
|
|
33
|
-
}
|
|
34
|
-
function isNameChar(c) {
|
|
35
|
-
return isNameStart(c) || (c >= 48 && c <= 57) || c === 45 || c === 46 || c === 58;
|
|
36
|
-
}
|
|
37
|
-
// characters that terminate a bare value
|
|
38
|
-
function isValueStop(c) {
|
|
39
|
-
return isSpace(c) || c === 123 || c === 125 || c === 59 || c === 34;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class CompactTokenizer {
|
|
43
|
-
constructor(text, lineIndex = null) {
|
|
44
|
-
this.s = text;
|
|
45
|
-
this.n = text.length;
|
|
46
|
-
this.i = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
47
|
-
this.lineIndex = lineIndex;
|
|
48
|
-
this.afterEq = false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fail(code, message, offset, length = 1, extra = {}) {
|
|
52
|
-
const d = new Diagnostic({ code, message, offset, length, ...extra });
|
|
53
|
-
d.locate(this.lineIndex);
|
|
54
|
-
throw new ES3DError([d], this.lineIndex);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
readQuoted(start) {
|
|
58
|
-
const s = this.s;
|
|
59
|
-
let i = start + 1;
|
|
60
|
-
let out = '';
|
|
61
|
-
let segStart = i;
|
|
62
|
-
for (;;) {
|
|
63
|
-
if (i >= this.n) this.fail('E_UNTERMINATED_STRING', 'Unterminated string: missing closing quote.', start, 1);
|
|
64
|
-
const c = s.charCodeAt(i);
|
|
65
|
-
if (c === 34) break;
|
|
66
|
-
if (c === 92) {
|
|
67
|
-
out += s.slice(segStart, i);
|
|
68
|
-
const e = s[i + 1];
|
|
69
|
-
if (e === '"' || e === '\\') out += e;
|
|
70
|
-
else if (e === 'n') out += '\n';
|
|
71
|
-
else if (e === 't') out += '\t';
|
|
72
|
-
else this.fail('E_BAD_ESCAPE', `Unknown escape sequence '\\${e ?? ''}'. Use \\" \\\\ \\n or \\t.`, i, 2);
|
|
73
|
-
i += 2;
|
|
74
|
-
segStart = i;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
i++;
|
|
78
|
-
}
|
|
79
|
-
out += s.slice(segStart, i);
|
|
80
|
-
return { value: out, end: i + 1, escaped: segStart !== start + 1 };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
next() {
|
|
84
|
-
const s = this.s;
|
|
85
|
-
let i = this.i;
|
|
86
|
-
while (i < this.n && isSpace(s.charCodeAt(i))) i++;
|
|
87
|
-
if (i >= this.n) {
|
|
88
|
-
this.i = i;
|
|
89
|
-
if (this.afterEq) this.fail('E_MISSING_VALUE', "Expected a value after '='.", i, 0);
|
|
90
|
-
return { type: CTok.EOF, start: i, end: i };
|
|
91
|
-
}
|
|
92
|
-
const c = s.charCodeAt(i);
|
|
93
|
-
const start = i;
|
|
94
|
-
|
|
95
|
-
if (this.afterEq) {
|
|
96
|
-
this.afterEq = false;
|
|
97
|
-
if (c === 34) {
|
|
98
|
-
const q = this.readQuoted(i);
|
|
99
|
-
this.i = q.end;
|
|
100
|
-
return { type: CTok.VALUE, value: q.value, start, end: q.end, valueStart: start + 1, valueEnd: q.end - 1, quote: '"', escaped: q.escaped };
|
|
101
|
-
}
|
|
102
|
-
if (c === 123 || c === 125 || c === 59) this.fail('E_MISSING_VALUE', "Expected a value after '='.", i, 1);
|
|
103
|
-
while (i < this.n && !isValueStop(s.charCodeAt(i))) i++;
|
|
104
|
-
this.i = i;
|
|
105
|
-
return { type: CTok.VALUE, value: s.slice(start, i), start, end: i, valueStart: start, valueEnd: i, quote: '' };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// comments
|
|
109
|
-
if (c === 47) {
|
|
110
|
-
const c2 = s.charCodeAt(i + 1);
|
|
111
|
-
if (c2 === 47) {
|
|
112
|
-
let end = s.indexOf('\n', i);
|
|
113
|
-
if (end < 0) end = this.n;
|
|
114
|
-
this.i = end;
|
|
115
|
-
return { type: CTok.COMMENT, text: s.slice(i + 2, end).replace(/\r$/, ''), start, end, line: true };
|
|
116
|
-
}
|
|
117
|
-
if (c2 === 42) {
|
|
118
|
-
const end = s.indexOf('*/', i + 2);
|
|
119
|
-
if (end < 0) this.fail('E_UNTERMINATED_COMMENT', "Unterminated comment: missing '*/'.", i, 2);
|
|
120
|
-
this.i = end + 2;
|
|
121
|
-
return { type: CTok.COMMENT, text: s.slice(i + 2, end), start, end: end + 2, line: false };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
switch (c) {
|
|
125
|
-
case 123:
|
|
126
|
-
this.i = i + 1;
|
|
127
|
-
return { type: CTok.LBRACE, start, end: i + 1 };
|
|
128
|
-
case 125:
|
|
129
|
-
this.i = i + 1;
|
|
130
|
-
return { type: CTok.RBRACE, start, end: i + 1 };
|
|
131
|
-
case 59:
|
|
132
|
-
this.i = i + 1;
|
|
133
|
-
return { type: CTok.SEMI, start, end: i + 1 };
|
|
134
|
-
case 61:
|
|
135
|
-
this.i = i + 1;
|
|
136
|
-
this.afterEq = true;
|
|
137
|
-
return { type: CTok.EQ, start, end: i + 1 };
|
|
138
|
-
case 34: {
|
|
139
|
-
const q = this.readQuoted(i);
|
|
140
|
-
this.i = q.end;
|
|
141
|
-
return { type: CTok.STRING, value: q.value, start, end: q.end, valueStart: start + 1, valueEnd: q.end - 1 };
|
|
142
|
-
}
|
|
143
|
-
case 35: // #id
|
|
144
|
-
case 64: {
|
|
145
|
-
// @ref
|
|
146
|
-
let j = i + 1;
|
|
147
|
-
if (!isNameStart(s.charCodeAt(j))) {
|
|
148
|
-
this.fail('E_SYNTAX', c === 35 ? "Expected an id after '#'." : "Expected a reference id after '@'.", i, 1);
|
|
149
|
-
}
|
|
150
|
-
while (j < this.n && isNameChar(s.charCodeAt(j))) j++;
|
|
151
|
-
this.i = j;
|
|
152
|
-
return { type: c === 35 ? CTok.ID : CTok.REF, value: s.slice(i + 1, j), start, end: j };
|
|
153
|
-
}
|
|
154
|
-
default:
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
if (isNameStart(c)) {
|
|
158
|
-
let j = i + 1;
|
|
159
|
-
while (j < this.n && isNameChar(s.charCodeAt(j))) j++;
|
|
160
|
-
this.i = j;
|
|
161
|
-
return { type: CTok.NAME, value: s.slice(i, j), start, end: j };
|
|
162
|
-
}
|
|
163
|
-
this.fail('E_SYNTAX', `Unexpected character '${s[i]}'.`, i, 1);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
1
|
+
// Tokenizer for the ES3D compact syntax ("ES3D-C").
|
|
2
|
+
//
|
|
3
|
+
// es3d version=1
|
|
4
|
+
// material#red c=#e53935
|
|
5
|
+
// box s=2 m=red
|
|
6
|
+
// group p=0,2,0 { sphere r=1 }
|
|
7
|
+
// use @tree p=10,0,5
|
|
8
|
+
//
|
|
9
|
+
// Newlines are insignificant: an element header ends where the next token is
|
|
10
|
+
// not an attribute (NAME '='), so the grammar is LL(2) and needs no schema.
|
|
11
|
+
|
|
12
|
+
import { Diagnostic, ES3DError } from './errors.js';
|
|
13
|
+
|
|
14
|
+
export const CTok = Object.freeze({
|
|
15
|
+
NAME: 'name',
|
|
16
|
+
ID: 'id', // #name
|
|
17
|
+
REF: 'ref', // @name
|
|
18
|
+
EQ: 'eq',
|
|
19
|
+
VALUE: 'value', // bare or quoted attribute value
|
|
20
|
+
STRING: 'string', // quoted text content in header position
|
|
21
|
+
LBRACE: 'lbrace',
|
|
22
|
+
RBRACE: 'rbrace',
|
|
23
|
+
SEMI: 'semi',
|
|
24
|
+
COMMENT: 'comment',
|
|
25
|
+
EOF: 'eof',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function isSpace(c) {
|
|
29
|
+
return c === 32 || c === 10 || c === 9 || c === 13;
|
|
30
|
+
}
|
|
31
|
+
function isNameStart(c) {
|
|
32
|
+
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
|
|
33
|
+
}
|
|
34
|
+
function isNameChar(c) {
|
|
35
|
+
return isNameStart(c) || (c >= 48 && c <= 57) || c === 45 || c === 46 || c === 58;
|
|
36
|
+
}
|
|
37
|
+
// characters that terminate a bare value
|
|
38
|
+
function isValueStop(c) {
|
|
39
|
+
return isSpace(c) || c === 123 || c === 125 || c === 59 || c === 34;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class CompactTokenizer {
|
|
43
|
+
constructor(text, lineIndex = null) {
|
|
44
|
+
this.s = text;
|
|
45
|
+
this.n = text.length;
|
|
46
|
+
this.i = text.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
47
|
+
this.lineIndex = lineIndex;
|
|
48
|
+
this.afterEq = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fail(code, message, offset, length = 1, extra = {}) {
|
|
52
|
+
const d = new Diagnostic({ code, message, offset, length, ...extra });
|
|
53
|
+
d.locate(this.lineIndex);
|
|
54
|
+
throw new ES3DError([d], this.lineIndex);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
readQuoted(start) {
|
|
58
|
+
const s = this.s;
|
|
59
|
+
let i = start + 1;
|
|
60
|
+
let out = '';
|
|
61
|
+
let segStart = i;
|
|
62
|
+
for (;;) {
|
|
63
|
+
if (i >= this.n) this.fail('E_UNTERMINATED_STRING', 'Unterminated string: missing closing quote.', start, 1);
|
|
64
|
+
const c = s.charCodeAt(i);
|
|
65
|
+
if (c === 34) break;
|
|
66
|
+
if (c === 92) {
|
|
67
|
+
out += s.slice(segStart, i);
|
|
68
|
+
const e = s[i + 1];
|
|
69
|
+
if (e === '"' || e === '\\') out += e;
|
|
70
|
+
else if (e === 'n') out += '\n';
|
|
71
|
+
else if (e === 't') out += '\t';
|
|
72
|
+
else this.fail('E_BAD_ESCAPE', `Unknown escape sequence '\\${e ?? ''}'. Use \\" \\\\ \\n or \\t.`, i, 2);
|
|
73
|
+
i += 2;
|
|
74
|
+
segStart = i;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
out += s.slice(segStart, i);
|
|
80
|
+
return { value: out, end: i + 1, escaped: segStart !== start + 1 };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
next() {
|
|
84
|
+
const s = this.s;
|
|
85
|
+
let i = this.i;
|
|
86
|
+
while (i < this.n && isSpace(s.charCodeAt(i))) i++;
|
|
87
|
+
if (i >= this.n) {
|
|
88
|
+
this.i = i;
|
|
89
|
+
if (this.afterEq) this.fail('E_MISSING_VALUE', "Expected a value after '='.", i, 0);
|
|
90
|
+
return { type: CTok.EOF, start: i, end: i };
|
|
91
|
+
}
|
|
92
|
+
const c = s.charCodeAt(i);
|
|
93
|
+
const start = i;
|
|
94
|
+
|
|
95
|
+
if (this.afterEq) {
|
|
96
|
+
this.afterEq = false;
|
|
97
|
+
if (c === 34) {
|
|
98
|
+
const q = this.readQuoted(i);
|
|
99
|
+
this.i = q.end;
|
|
100
|
+
return { type: CTok.VALUE, value: q.value, start, end: q.end, valueStart: start + 1, valueEnd: q.end - 1, quote: '"', escaped: q.escaped };
|
|
101
|
+
}
|
|
102
|
+
if (c === 123 || c === 125 || c === 59) this.fail('E_MISSING_VALUE', "Expected a value after '='.", i, 1);
|
|
103
|
+
while (i < this.n && !isValueStop(s.charCodeAt(i))) i++;
|
|
104
|
+
this.i = i;
|
|
105
|
+
return { type: CTok.VALUE, value: s.slice(start, i), start, end: i, valueStart: start, valueEnd: i, quote: '' };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// comments
|
|
109
|
+
if (c === 47) {
|
|
110
|
+
const c2 = s.charCodeAt(i + 1);
|
|
111
|
+
if (c2 === 47) {
|
|
112
|
+
let end = s.indexOf('\n', i);
|
|
113
|
+
if (end < 0) end = this.n;
|
|
114
|
+
this.i = end;
|
|
115
|
+
return { type: CTok.COMMENT, text: s.slice(i + 2, end).replace(/\r$/, ''), start, end, line: true };
|
|
116
|
+
}
|
|
117
|
+
if (c2 === 42) {
|
|
118
|
+
const end = s.indexOf('*/', i + 2);
|
|
119
|
+
if (end < 0) this.fail('E_UNTERMINATED_COMMENT', "Unterminated comment: missing '*/'.", i, 2);
|
|
120
|
+
this.i = end + 2;
|
|
121
|
+
return { type: CTok.COMMENT, text: s.slice(i + 2, end), start, end: end + 2, line: false };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
switch (c) {
|
|
125
|
+
case 123:
|
|
126
|
+
this.i = i + 1;
|
|
127
|
+
return { type: CTok.LBRACE, start, end: i + 1 };
|
|
128
|
+
case 125:
|
|
129
|
+
this.i = i + 1;
|
|
130
|
+
return { type: CTok.RBRACE, start, end: i + 1 };
|
|
131
|
+
case 59:
|
|
132
|
+
this.i = i + 1;
|
|
133
|
+
return { type: CTok.SEMI, start, end: i + 1 };
|
|
134
|
+
case 61:
|
|
135
|
+
this.i = i + 1;
|
|
136
|
+
this.afterEq = true;
|
|
137
|
+
return { type: CTok.EQ, start, end: i + 1 };
|
|
138
|
+
case 34: {
|
|
139
|
+
const q = this.readQuoted(i);
|
|
140
|
+
this.i = q.end;
|
|
141
|
+
return { type: CTok.STRING, value: q.value, start, end: q.end, valueStart: start + 1, valueEnd: q.end - 1 };
|
|
142
|
+
}
|
|
143
|
+
case 35: // #id
|
|
144
|
+
case 64: {
|
|
145
|
+
// @ref
|
|
146
|
+
let j = i + 1;
|
|
147
|
+
if (!isNameStart(s.charCodeAt(j))) {
|
|
148
|
+
this.fail('E_SYNTAX', c === 35 ? "Expected an id after '#'." : "Expected a reference id after '@'.", i, 1);
|
|
149
|
+
}
|
|
150
|
+
while (j < this.n && isNameChar(s.charCodeAt(j))) j++;
|
|
151
|
+
this.i = j;
|
|
152
|
+
return { type: c === 35 ? CTok.ID : CTok.REF, value: s.slice(i + 1, j), start, end: j };
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (isNameStart(c)) {
|
|
158
|
+
let j = i + 1;
|
|
159
|
+
while (j < this.n && isNameChar(s.charCodeAt(j))) j++;
|
|
160
|
+
this.i = j;
|
|
161
|
+
return { type: CTok.NAME, value: s.slice(i, j), start, end: j };
|
|
162
|
+
}
|
|
163
|
+
this.fail('E_SYNTAX', `Unexpected character '${s[i]}'.`, i, 1);
|
|
164
|
+
}
|
|
165
|
+
}
|