@es3d/es3d 1.0.1 → 1.0.2
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/README.md +11 -2
- package/docs/SPEC.md +9 -0
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +29 -16
- package/src/format/parse.js +43 -3
- package/src/index.js +2 -2
- package/src/io/gltf-pack.js +1461 -0
- package/src/optimize/optimizer.js +34 -9
- package/src/render/stage.js +2 -1
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// detectPatterns sibling copies whose transforms form a progression -> <repeat>
|
|
7
7
|
// collapseGroups drop empty groups and attribute-less single-child groups
|
|
8
8
|
// weldMeshes merge duplicate vertices of <mesh> elements
|
|
9
|
-
// removeUnused drop definitions and materials nothing references
|
|
9
|
+
// removeUnused drop definitions and materials nothing references,
|
|
10
|
+
// counting ids named by an x- attribute
|
|
10
11
|
// precision quantize numbers when serializing (lossy, opt-in)
|
|
11
12
|
//
|
|
12
13
|
// plus expandProcedural(), the inverse operation used by the benchmark: it
|
|
@@ -163,21 +164,36 @@ function stripDefaults(root, report) {
|
|
|
163
164
|
if (n) report.push({ pass: 'stripDefaults', detail: `removed ${n} attribute${n === 1 ? '' : 's'} equal to the default` });
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
/** An id token as written in an attribute: an optional leading # is not part of the id. */
|
|
168
|
+
function idToken(token) {
|
|
169
|
+
return token.replace(/^#/, '');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Rewrite id tokens inside an x- attribute, keeping the original separators and any leading #. */
|
|
173
|
+
function rewriteExtensionTokens(value, mapping) {
|
|
174
|
+
return value.replace(/[^\s,]+/g, (token) => {
|
|
175
|
+
const id = idToken(token);
|
|
176
|
+
if (!mapping.has(id)) return token;
|
|
177
|
+
return (token.startsWith('#') ? '#' : '') + mapping.get(id);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
function rewriteRefs(root, mapping, kinds) {
|
|
167
182
|
walkAll(root, (el) => {
|
|
168
183
|
for (const a of el.attrs) {
|
|
169
184
|
const k = canonicalAttr(el, a);
|
|
170
185
|
const s = attrSpec(el.name, k);
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
const id = a.value.trim().replace(/^#/, '');
|
|
186
|
+
if (s && s.type === 'ref' && kinds.includes(s.ref)) {
|
|
187
|
+
const id = idToken(a.value.trim());
|
|
174
188
|
if (mapping.has(id)) a.value = mapping.get(id);
|
|
175
|
-
} else if (s.type === 'idlist' && kinds.includes(s.ref)) {
|
|
189
|
+
} else if (s && s.type === 'idlist' && kinds.includes(s.ref)) {
|
|
176
190
|
a.value = a.value
|
|
177
191
|
.split(/[\s,]+/)
|
|
178
192
|
.filter(Boolean)
|
|
179
|
-
.map((id) => mapping.get(id
|
|
193
|
+
.map((id) => mapping.get(idToken(id)) || id)
|
|
180
194
|
.join(',');
|
|
195
|
+
} else if (!s && el.name.startsWith('x-')) {
|
|
196
|
+
a.value = rewriteExtensionTokens(a.value, mapping);
|
|
181
197
|
}
|
|
182
198
|
}
|
|
183
199
|
});
|
|
@@ -211,12 +227,21 @@ function dedupeMaterials(root, report) {
|
|
|
211
227
|
|
|
212
228
|
function collectReferences(root) {
|
|
213
229
|
const used = new Set();
|
|
230
|
+
// x- attributes are not in the schema. A token counts only when it names an id
|
|
231
|
+
// that exists, so prose in an extension cannot pin an unrelated definition.
|
|
232
|
+
const ids = usedIds(root);
|
|
214
233
|
walkAll(root, (el) => {
|
|
215
234
|
for (const a of el.attrs) {
|
|
216
235
|
const s = attrSpec(el.name, canonicalAttr(el, a));
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
236
|
+
if (s) {
|
|
237
|
+
if (s.type === 'ref') used.add(idToken(a.value.trim()));
|
|
238
|
+
else if (s.type === 'idlist') for (const id of a.value.split(/[\s,]+/)) if (id) used.add(idToken(id));
|
|
239
|
+
} else if (el.name.startsWith('x-')) {
|
|
240
|
+
for (const token of a.value.split(/[\s,]+/)) {
|
|
241
|
+
const id = idToken(token);
|
|
242
|
+
if (id && ids.has(id)) used.add(id);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
220
245
|
}
|
|
221
246
|
});
|
|
222
247
|
return used;
|
package/src/render/stage.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// automatic orbit; scroll-driven cameras jump between stops.
|
|
24
24
|
|
|
25
25
|
import { compile } from '../pipeline.js';
|
|
26
|
+
import { formatDiagnostics } from '../format/parse.js';
|
|
26
27
|
import { updateDynamic } from '../scene/flatten.js';
|
|
27
28
|
import { pick } from '../scene/picking.js';
|
|
28
29
|
import { createRenderer, PROGRESSIVE_BUDGET_MS } from './renderer.js';
|
|
@@ -267,7 +268,7 @@ export async function createStage(canvas, opts = {}) {
|
|
|
267
268
|
// and the renderer finds those meshes already on the GPU.
|
|
268
269
|
const result = compile(source, { time: state.time, progress: state.progress, cache: state.result ? state.result.cache : undefined });
|
|
269
270
|
if (!result.ok) {
|
|
270
|
-
const msg = result
|
|
271
|
+
const msg = formatDiagnostics(result);
|
|
271
272
|
if (opts.onError) opts.onError(msg, result);
|
|
272
273
|
else console.error(msg);
|
|
273
274
|
return result;
|