@plasius/gpu-model-core 0.1.0 → 0.3.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/README.md +131 -0
- package/dist/index.cjs +679 -214
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +239 -1
- package/dist/index.d.ts +239 -1
- package/dist/index.js +669 -214
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,8 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
CANONICAL_GPU_MODEL_COORDINATE_SYSTEM: () => CANONICAL_GPU_MODEL_COORDINATE_SYSTEM,
|
|
24
|
+
GPU_MODEL_DIAGNOSTIC_LIMITS: () => GPU_MODEL_DIAGNOSTIC_LIMITS,
|
|
24
25
|
GPU_MODEL_DOCUMENT_LIMITS: () => GPU_MODEL_DOCUMENT_LIMITS,
|
|
25
26
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION: () => GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
27
|
+
GPU_MODEL_REPAIR_RULES: () => GPU_MODEL_REPAIR_RULES,
|
|
28
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS: () => GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
29
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION: () => GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
26
30
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS: () => GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
27
31
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG: () => GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
28
32
|
GPU_MODEL_STATIC_DEMO_LIMITS: () => GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -31,15 +35,21 @@ __export(index_exports, {
|
|
|
31
35
|
GPU_MODEL_STATIC_DEMO_MAX_EXTENT_METRES: () => GPU_MODEL_STATIC_DEMO_MAX_EXTENT_METRES,
|
|
32
36
|
GPU_MODEL_STATIC_DEMO_NORMALIZATION_CONTRACT: () => GPU_MODEL_STATIC_DEMO_NORMALIZATION_CONTRACT,
|
|
33
37
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION: () => GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
38
|
+
GpuModelDiagnosticsError: () => GpuModelDiagnosticsError,
|
|
34
39
|
GpuModelDocumentError: () => GpuModelDocumentError,
|
|
40
|
+
GpuModelResourceGraphError: () => GpuModelResourceGraphError,
|
|
35
41
|
assertGpuModelStaticDemoNormalization: () => assertGpuModelStaticDemoNormalization,
|
|
36
42
|
canCreateGpuModelDocument: () => canCreateGpuModelDocument,
|
|
37
43
|
createAndVerifyGpuModelDocument: () => createAndVerifyGpuModelDocument,
|
|
38
44
|
createGpuModelDocument: () => createGpuModelDocument,
|
|
45
|
+
createGpuModelResourceGraph: () => createGpuModelResourceGraph,
|
|
39
46
|
createGpuModelStaticDemoCompilerInput: () => createGpuModelStaticDemoCompilerInput,
|
|
40
47
|
encodeGpuModelDocumentCanonical: () => encodeGpuModelDocumentCanonical,
|
|
48
|
+
evaluateGpuModelDiagnostics: () => evaluateGpuModelDiagnostics,
|
|
49
|
+
gpuModelResourceKey: () => gpuModelResourceKey,
|
|
41
50
|
hashGpuModelDocumentCanonical: () => hashGpuModelDocumentCanonical,
|
|
42
51
|
isGpuModelDocument: () => isGpuModelDocument,
|
|
52
|
+
isGpuModelResourceGraph: () => isGpuModelResourceGraph,
|
|
43
53
|
packageBootstrap: () => packageBootstrap,
|
|
44
54
|
packageName: () => packageName,
|
|
45
55
|
verifyGpuModelResource: () => verifyGpuModelResource
|
|
@@ -220,9 +230,9 @@ function readRecord(value, path, allowedKeys) {
|
|
|
220
230
|
}
|
|
221
231
|
return value;
|
|
222
232
|
}
|
|
223
|
-
function requireKey(
|
|
224
|
-
if (!Object.hasOwn(
|
|
225
|
-
return
|
|
233
|
+
function requireKey(record3, key, path) {
|
|
234
|
+
if (!Object.hasOwn(record3, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
|
|
235
|
+
return record3[key];
|
|
226
236
|
}
|
|
227
237
|
function readArray(value, path, maximum, minimum = 0) {
|
|
228
238
|
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
@@ -301,9 +311,9 @@ function readNumberTuple(value, path, length, minimum, maximum) {
|
|
|
301
311
|
const input = readArray(value, path, length, length);
|
|
302
312
|
return Object.freeze(input.map((entry, index) => readFinite(entry, `${path}[${index}]`, minimum, maximum)));
|
|
303
313
|
}
|
|
304
|
-
function readOptional(
|
|
305
|
-
if (!Object.hasOwn(
|
|
306
|
-
return parser(
|
|
314
|
+
function readOptional(record3, key, parser, path) {
|
|
315
|
+
if (!Object.hasOwn(record3, key)) return void 0;
|
|
316
|
+
return parser(record3[key], `${path}.${key}`);
|
|
307
317
|
}
|
|
308
318
|
function assignOptional(target, key, value) {
|
|
309
319
|
if (value !== void 0) Object.assign(target, { [key]: value });
|
|
@@ -325,17 +335,17 @@ function readMetadataValue(value, path, depth, budget) {
|
|
|
325
335
|
return Object.freeze(input.map((entry, index) => readMetadataValue(entry, `${path}[${index}]`, depth + 1, budget)));
|
|
326
336
|
}
|
|
327
337
|
if (typeof value !== "object" || value === null) return fail("invalid-type", path, "metadata value is not JSON-compatible");
|
|
328
|
-
const
|
|
329
|
-
const prototype = Object.getPrototypeOf(
|
|
338
|
+
const record3 = value;
|
|
339
|
+
const prototype = Object.getPrototypeOf(record3);
|
|
330
340
|
if (prototype !== Object.prototype && prototype !== null) return fail("invalid-type", path, "metadata objects must be plain objects");
|
|
331
|
-
const keys = Reflect.ownKeys(
|
|
341
|
+
const keys = Reflect.ownKeys(record3);
|
|
332
342
|
if (keys.length > GPU_MODEL_DOCUMENT_LIMITS.metadataObjectKeys) return fail("limit-exceeded", path, "metadata object has too many keys");
|
|
333
343
|
const output = {};
|
|
334
344
|
for (const key of keys) {
|
|
335
345
|
if (typeof key !== "string") return fail("unknown-key", path, "metadata symbol keys are not permitted");
|
|
336
346
|
if (PROHIBITED_KEYS.has(key)) return fail("unknown-key", `${path}.${key}`, "prohibited metadata key");
|
|
337
347
|
readString(key, `${path}.[key]`, GPU_MODEL_DOCUMENT_LIMITS.metadataKeyLength, METADATA_KEY);
|
|
338
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
348
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, key);
|
|
339
349
|
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) return fail("invalid-type", `${path}.${key}`, "metadata properties must be enumerable data properties");
|
|
340
350
|
output[key] = readMetadataValue(descriptor.value, `${path}.${key}`, depth + 1, budget);
|
|
341
351
|
}
|
|
@@ -348,9 +358,9 @@ function readMetadata(value, path, budget) {
|
|
|
348
358
|
}
|
|
349
359
|
function parseCoordinateSystem(value) {
|
|
350
360
|
const path = "$.coordinateSystem";
|
|
351
|
-
const
|
|
361
|
+
const record3 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
|
|
352
362
|
for (const [key, expected] of Object.entries(CANONICAL_GPU_MODEL_COORDINATE_SYSTEM)) {
|
|
353
|
-
if (requireKey(
|
|
363
|
+
if (requireKey(record3, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
|
|
354
364
|
}
|
|
355
365
|
return CANONICAL_GPU_MODEL_COORDINATE_SYSTEM;
|
|
356
366
|
}
|
|
@@ -362,19 +372,19 @@ function parseMatrix(value, path, requireAffine = true) {
|
|
|
362
372
|
return matrix;
|
|
363
373
|
}
|
|
364
374
|
function parseBounds(value, path) {
|
|
365
|
-
const
|
|
366
|
-
const min = readNumberTuple(requireKey(
|
|
367
|
-
const max = readNumberTuple(requireKey(
|
|
375
|
+
const record3 = readRecord(value, path, ["min", "max"]);
|
|
376
|
+
const min = readNumberTuple(requireKey(record3, "min", path), `${path}.min`, 3);
|
|
377
|
+
const max = readNumberTuple(requireKey(record3, "max", path), `${path}.max`, 3);
|
|
368
378
|
for (let index = 0; index < 3; index += 1) {
|
|
369
379
|
if (min[index] > max[index]) return fail("invalid-value", path, "bounds min cannot exceed max");
|
|
370
380
|
}
|
|
371
381
|
return { min, max };
|
|
372
382
|
}
|
|
373
383
|
function parseTransformTrs(value, path) {
|
|
374
|
-
const
|
|
375
|
-
const translation = readNumberTuple(requireKey(
|
|
376
|
-
const rotation = readNumberTuple(requireKey(
|
|
377
|
-
const scale = readNumberTuple(requireKey(
|
|
384
|
+
const record3 = readRecord(value, path, ["translation", "rotation", "scale"]);
|
|
385
|
+
const translation = readNumberTuple(requireKey(record3, "translation", path), `${path}.translation`, 3);
|
|
386
|
+
const rotation = readNumberTuple(requireKey(record3, "rotation", path), `${path}.rotation`, 4);
|
|
387
|
+
const scale = readNumberTuple(requireKey(record3, "scale", path), `${path}.scale`, 3);
|
|
378
388
|
const magnitude = Math.hypot(...rotation);
|
|
379
389
|
if (!almostEqual(magnitude, 1, 1e-4)) return fail("invalid-value", `${path}.rotation`, "rotation quaternion must be normalized");
|
|
380
390
|
return { translation, rotation, scale };
|
|
@@ -396,25 +406,25 @@ function intrinsicBlobArrayBuffer(blob) {
|
|
|
396
406
|
return Reflect.apply(blobArrayBuffer, blob, []);
|
|
397
407
|
}
|
|
398
408
|
function parseResourceHeader(value, path) {
|
|
399
|
-
const
|
|
400
|
-
const kind = readEnum(requireKey(
|
|
401
|
-
const mimeType = readString(requireKey(
|
|
409
|
+
const record3 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
|
|
410
|
+
const kind = readEnum(requireKey(record3, "kind", path), `${path}.kind`, ["buffer", "image"]);
|
|
411
|
+
const mimeType = readString(requireKey(record3, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
|
|
402
412
|
if (kind === "image" ? !IMAGE_MIME_TYPES.has(mimeType) : !BUFFER_MIME_TYPES.has(mimeType)) {
|
|
403
413
|
return fail("invalid-value", `${path}.mimeType`, `unsupported ${kind} MIME type`);
|
|
404
414
|
}
|
|
405
|
-
const payload = requireKey(
|
|
415
|
+
const payload = requireKey(record3, "payload", path);
|
|
406
416
|
if (!(payload instanceof Blob)) return fail("invalid-type", `${path}.payload`, "expected an immutable Blob payload");
|
|
407
417
|
if (Object.getPrototypeOf(payload) !== Blob.prototype || Reflect.ownKeys(payload).some((key) => typeof key === "string")) {
|
|
408
418
|
return fail("invalid-type", `${path}.payload`, "expected a native Blob without caller-defined properties");
|
|
409
419
|
}
|
|
410
|
-
const byteLength = readSafeInteger(requireKey(
|
|
420
|
+
const byteLength = readSafeInteger(requireKey(record3, "byteLength", path), `${path}.byteLength`, 1);
|
|
411
421
|
if (intrinsicBlobSize(payload) !== byteLength) return fail("payload-length-mismatch", `${path}.payload`, "payload byte length does not match byteLength");
|
|
412
422
|
const payloadType = intrinsicBlobType(payload);
|
|
413
423
|
if (payloadType && payloadType.toLowerCase() !== mimeType) return fail("invalid-value", `${path}.payload`, "payload MIME type does not match mimeType");
|
|
414
424
|
return {
|
|
415
|
-
id: readId(requireKey(
|
|
425
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
416
426
|
kind,
|
|
417
|
-
contentHash: readSha256(requireKey(
|
|
427
|
+
contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
|
|
418
428
|
byteLength,
|
|
419
429
|
mimeType,
|
|
420
430
|
payload
|
|
@@ -423,11 +433,11 @@ function parseResourceHeader(value, path) {
|
|
|
423
433
|
function resolveVerificationLimits(options) {
|
|
424
434
|
const overrides = options?.limits;
|
|
425
435
|
if (overrides === void 0) return GPU_MODEL_RESOURCE_VERIFICATION_LIMITS;
|
|
426
|
-
const
|
|
436
|
+
const record3 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
|
|
427
437
|
const output = { ...GPU_MODEL_RESOURCE_VERIFICATION_LIMITS };
|
|
428
438
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS)) {
|
|
429
|
-
if (!Object.hasOwn(
|
|
430
|
-
const value = readSafeInteger(
|
|
439
|
+
if (!Object.hasOwn(record3, key)) continue;
|
|
440
|
+
const value = readSafeInteger(record3[key], `$.verification.limits.${key}`, 1, ceiling);
|
|
431
441
|
Object.assign(output, { [key]: value });
|
|
432
442
|
}
|
|
433
443
|
if (output.maxImageBytes > output.maxResourceBytes) output.maxImageBytes = output.maxResourceBytes;
|
|
@@ -480,7 +490,7 @@ function verificationAbortError(signal, path) {
|
|
|
480
490
|
function throwIfVerificationAborted(signal, path) {
|
|
481
491
|
if (signal.aborted) throw verificationAbortError(signal, path);
|
|
482
492
|
}
|
|
483
|
-
function createBlobChunks(blob, chunkBytes, observed, signal, path,
|
|
493
|
+
function createBlobChunks(blob, chunkBytes, observed, signal, path, snapshot2) {
|
|
484
494
|
return {
|
|
485
495
|
async *[Symbol.asyncIterator]() {
|
|
486
496
|
const size = intrinsicBlobSize(blob);
|
|
@@ -490,7 +500,7 @@ function createBlobChunks(blob, chunkBytes, observed, signal, path, snapshot) {
|
|
|
490
500
|
const chunk = new Uint8Array(await intrinsicBlobArrayBuffer(intrinsicBlobSlice(blob, offset, end)));
|
|
491
501
|
throwIfVerificationAborted(signal, path);
|
|
492
502
|
observed.bytes += chunk.byteLength;
|
|
493
|
-
if (
|
|
503
|
+
if (snapshot2) snapshot2.set(chunk, offset);
|
|
494
504
|
yield chunk;
|
|
495
505
|
}
|
|
496
506
|
throwIfVerificationAborted(signal, path);
|
|
@@ -539,13 +549,13 @@ function createVerificationDeadline(timeoutMs, callerSignal, path) {
|
|
|
539
549
|
};
|
|
540
550
|
}
|
|
541
551
|
function parseInspection(value, path) {
|
|
542
|
-
const
|
|
552
|
+
const record3 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
|
|
543
553
|
const output = {
|
|
544
|
-
valid: readBoolean(requireKey(
|
|
545
|
-
detectedMimeType: readString(requireKey(
|
|
554
|
+
valid: readBoolean(requireKey(record3, "valid", path), `${path}.valid`),
|
|
555
|
+
detectedMimeType: readString(requireKey(record3, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
|
|
546
556
|
};
|
|
547
|
-
assignOptional(output, "width", readOptional(
|
|
548
|
-
assignOptional(output, "height", readOptional(
|
|
557
|
+
assignOptional(output, "width", readOptional(record3, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
558
|
+
assignOptional(output, "height", readOptional(record3, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
549
559
|
return output;
|
|
550
560
|
}
|
|
551
561
|
function bytesStartWith(bytes, signature) {
|
|
@@ -618,12 +628,12 @@ async function verifyParsedResource(resource, port, limits, options, path = "$.r
|
|
|
618
628
|
} else if (inspection.width !== void 0 || inspection.height !== void 0) {
|
|
619
629
|
return fail("resource-verification-failed", path, "buffer inspection must not report image dimensions");
|
|
620
630
|
}
|
|
621
|
-
const
|
|
631
|
+
const snapshot2 = new Uint8Array(resource.byteLength);
|
|
622
632
|
const digestObserved = { bytes: 0 };
|
|
623
633
|
let digest;
|
|
624
634
|
try {
|
|
625
635
|
digest = await deadline.race(() => port.digestSha256(
|
|
626
|
-
createBlobChunks(resource.payload, limits.chunkBytes, digestObserved, deadline.signal, path,
|
|
636
|
+
createBlobChunks(resource.payload, limits.chunkBytes, digestObserved, deadline.signal, path, snapshot2),
|
|
627
637
|
context
|
|
628
638
|
));
|
|
629
639
|
} catch (error) {
|
|
@@ -636,13 +646,13 @@ async function verifyParsedResource(resource, port, limits, options, path = "$.r
|
|
|
636
646
|
if (typeof digest !== "string" || !SHA_256.test(digest) || digest !== resource.contentHash) {
|
|
637
647
|
return fail("resource-verification-failed", path, "resource digest does not match contentHash");
|
|
638
648
|
}
|
|
639
|
-
if (resource.kind === "image") validateImageMagic(resource,
|
|
649
|
+
if (resource.kind === "image") validateImageMagic(resource, snapshot2, inspection, path);
|
|
640
650
|
const verified = Object.freeze({
|
|
641
651
|
...resource,
|
|
642
|
-
payload: new Blob([
|
|
652
|
+
payload: new Blob([snapshot2], { type: resource.mimeType })
|
|
643
653
|
});
|
|
644
654
|
verifiedResources.add(verified);
|
|
645
|
-
verifiedResourceBytes.set(verified,
|
|
655
|
+
verifiedResourceBytes.set(verified, snapshot2);
|
|
646
656
|
verifiedResourceEvidence.set(verified, Object.freeze({ ...inspection }));
|
|
647
657
|
return verified;
|
|
648
658
|
} finally {
|
|
@@ -660,31 +670,31 @@ async function verifyGpuModelResource(value, port, options) {
|
|
|
660
670
|
}
|
|
661
671
|
}
|
|
662
672
|
function parseNode(value, path, budget) {
|
|
663
|
-
const
|
|
664
|
-
const children = Object.freeze(readArray(requireKey(
|
|
673
|
+
const record3 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
|
|
674
|
+
const children = Object.freeze(readArray(requireKey(record3, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
|
|
665
675
|
consumeBudget(budget, "sceneEdges", children.length, GPU_MODEL_DOCUMENT_LIMITS.sceneEdges, path, "scene edge");
|
|
666
676
|
ensureUnique(children, `${path}.children`, "duplicate child node");
|
|
667
677
|
const output = {
|
|
668
|
-
id: readId(requireKey(
|
|
678
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
669
679
|
children,
|
|
670
|
-
localMatrix: parseMatrix(requireKey(
|
|
680
|
+
localMatrix: parseMatrix(requireKey(record3, "localMatrix", path), `${path}.localMatrix`)
|
|
671
681
|
};
|
|
672
|
-
assignOptional(output, "name", readOptional(
|
|
673
|
-
assignOptional(output, "meshId", readOptional(
|
|
674
|
-
assignOptional(output, "skinId", readOptional(
|
|
675
|
-
assignOptional(output, "metadata", readOptional(
|
|
682
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
683
|
+
assignOptional(output, "meshId", readOptional(record3, "meshId", readId, path));
|
|
684
|
+
assignOptional(output, "skinId", readOptional(record3, "skinId", readId, path));
|
|
685
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
676
686
|
return output;
|
|
677
687
|
}
|
|
678
688
|
function parseAccessor(value, path, budget) {
|
|
679
|
-
const
|
|
680
|
-
const componentType = readEnum(requireKey(
|
|
681
|
-
const elementType = readEnum(requireKey(
|
|
682
|
-
const normalized = readOptional(
|
|
689
|
+
const record3 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
|
|
690
|
+
const componentType = readEnum(requireKey(record3, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
|
|
691
|
+
const elementType = readEnum(requireKey(record3, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
|
|
692
|
+
const normalized = readOptional(record3, "normalized", readBoolean, path);
|
|
683
693
|
if (normalized === true && componentType === "f32") return fail("invalid-value", `${path}.normalized`, "floating-point accessors cannot be normalized");
|
|
684
694
|
if (elementType.startsWith("mat") && componentType !== "f32") return fail("invalid-value", `${path}.componentType`, "matrix accessors must use f32 components");
|
|
685
695
|
const expectedComponents = ELEMENT_COMPONENTS[elementType];
|
|
686
|
-
const min = readOptional(
|
|
687
|
-
const max = readOptional(
|
|
696
|
+
const min = readOptional(record3, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
697
|
+
const max = readOptional(record3, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
688
698
|
if (min === void 0 !== (max === void 0)) return fail("invalid-value", path, "accessor min and max must be supplied together");
|
|
689
699
|
if (min && max) {
|
|
690
700
|
const [componentMinimum, componentMaximum, integer] = COMPONENT_RANGES[componentType];
|
|
@@ -697,25 +707,25 @@ function parseAccessor(value, path, budget) {
|
|
|
697
707
|
}
|
|
698
708
|
}
|
|
699
709
|
}
|
|
700
|
-
const count = readSafeInteger(requireKey(
|
|
710
|
+
const count = readSafeInteger(requireKey(record3, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
|
|
701
711
|
consumeBudget(budget, "accessorElements", count * expectedComponents, GPU_MODEL_DOCUMENT_LIMITS.accessorElements, path, "accessor element component");
|
|
702
712
|
const output = {
|
|
703
|
-
id: readId(requireKey(
|
|
704
|
-
resourceId: readId(requireKey(
|
|
705
|
-
byteOffset: readSafeInteger(requireKey(
|
|
713
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
714
|
+
resourceId: readId(requireKey(record3, "resourceId", path), `${path}.resourceId`),
|
|
715
|
+
byteOffset: readSafeInteger(requireKey(record3, "byteOffset", path), `${path}.byteOffset`),
|
|
706
716
|
count,
|
|
707
717
|
componentType,
|
|
708
718
|
elementType
|
|
709
719
|
};
|
|
710
|
-
assignOptional(output, "byteStride", readOptional(
|
|
720
|
+
assignOptional(output, "byteStride", readOptional(record3, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
|
|
711
721
|
assignOptional(output, "normalized", normalized);
|
|
712
722
|
assignOptional(output, "min", min);
|
|
713
723
|
assignOptional(output, "max", max);
|
|
714
724
|
return output;
|
|
715
725
|
}
|
|
716
726
|
function parsePrimitive(value, path, budget) {
|
|
717
|
-
const
|
|
718
|
-
const attributesInput = readArray(requireKey(
|
|
727
|
+
const record3 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
|
|
728
|
+
const attributesInput = readArray(requireKey(record3, "attributes", path), `${path}.attributes`, 64, 1);
|
|
719
729
|
consumeBudget(budget, "primitiveAttributes", attributesInput.length, GPU_MODEL_DOCUMENT_LIMITS.primitiveAttributes, path, "primitive attribute");
|
|
720
730
|
const attributes = Object.freeze(attributesInput.map((entry, index) => {
|
|
721
731
|
const entryPath = `${path}.attributes[${index}]`;
|
|
@@ -728,53 +738,53 @@ function parsePrimitive(value, path, budget) {
|
|
|
728
738
|
ensureUnique(attributes.map(({ semantic }) => semantic), `${path}.attributes`, "duplicate attribute semantic");
|
|
729
739
|
if (!attributes.some(({ semantic }) => semantic === "POSITION")) return fail("invalid-value", `${path}.attributes`, "primitive requires POSITION");
|
|
730
740
|
const output = {
|
|
731
|
-
id: readId(requireKey(
|
|
732
|
-
topology: readEnum(requireKey(
|
|
741
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
742
|
+
topology: readEnum(requireKey(record3, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
|
|
733
743
|
attributes
|
|
734
744
|
};
|
|
735
|
-
assignOptional(output, "indicesAccessorId", readOptional(
|
|
736
|
-
assignOptional(output, "materialId", readOptional(
|
|
737
|
-
assignOptional(output, "metadata", readOptional(
|
|
745
|
+
assignOptional(output, "indicesAccessorId", readOptional(record3, "indicesAccessorId", readId, path));
|
|
746
|
+
assignOptional(output, "materialId", readOptional(record3, "materialId", readId, path));
|
|
747
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
738
748
|
return output;
|
|
739
749
|
}
|
|
740
750
|
function parseMesh(value, path, budget) {
|
|
741
|
-
const
|
|
742
|
-
const input = readArray(requireKey(
|
|
751
|
+
const record3 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
|
|
752
|
+
const input = readArray(requireKey(record3, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
743
753
|
consumeBudget(budget, "primitives", input.length, GPU_MODEL_DOCUMENT_LIMITS.primitives, path, "primitive");
|
|
744
754
|
const output = {
|
|
745
|
-
id: readId(requireKey(
|
|
755
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
746
756
|
primitives: Object.freeze(input.map((entry, index) => parsePrimitive(entry, `${path}.primitives[${index}]`, budget)))
|
|
747
757
|
};
|
|
748
|
-
assignOptional(output, "name", readOptional(
|
|
749
|
-
assignOptional(output, "metadata", readOptional(
|
|
758
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
759
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
750
760
|
return output;
|
|
751
761
|
}
|
|
752
762
|
function parseTextureTransform(value, path) {
|
|
753
|
-
const
|
|
754
|
-
if (Reflect.ownKeys(
|
|
763
|
+
const record3 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
|
|
764
|
+
if (Reflect.ownKeys(record3).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
|
|
755
765
|
const output = {};
|
|
756
|
-
assignOptional(output, "offset", readOptional(
|
|
757
|
-
assignOptional(output, "rotationRadians", readOptional(
|
|
758
|
-
assignOptional(output, "scale", readOptional(
|
|
766
|
+
assignOptional(output, "offset", readOptional(record3, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
767
|
+
assignOptional(output, "rotationRadians", readOptional(record3, "rotationRadians", readFinite, path));
|
|
768
|
+
assignOptional(output, "scale", readOptional(record3, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
759
769
|
return output;
|
|
760
770
|
}
|
|
761
771
|
function parseTextureBinding(value, path) {
|
|
762
|
-
const
|
|
772
|
+
const record3 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
|
|
763
773
|
const output = {
|
|
764
|
-
textureId: readId(requireKey(
|
|
765
|
-
colorSpace: readEnum(requireKey(
|
|
766
|
-
intendedUsage: readEnum(requireKey(
|
|
774
|
+
textureId: readId(requireKey(record3, "textureId", path), `${path}.textureId`),
|
|
775
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
776
|
+
intendedUsage: readEnum(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
|
|
767
777
|
};
|
|
768
|
-
assignOptional(output, "samplerId", readOptional(
|
|
769
|
-
assignOptional(output, "texCoordSet", readOptional(
|
|
770
|
-
assignOptional(output, "transform", readOptional(
|
|
778
|
+
assignOptional(output, "samplerId", readOptional(record3, "samplerId", readId, path));
|
|
779
|
+
assignOptional(output, "texCoordSet", readOptional(record3, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
|
|
780
|
+
assignOptional(output, "transform", readOptional(record3, "transform", parseTextureTransform, path));
|
|
771
781
|
return output;
|
|
772
782
|
}
|
|
773
783
|
function parseMaterialTextures(value, path) {
|
|
774
784
|
const slots = Object.keys(MATERIAL_TEXTURE_SLOTS);
|
|
775
|
-
const
|
|
785
|
+
const record3 = readRecord(value, path, slots);
|
|
776
786
|
const output = {};
|
|
777
|
-
for (const slot of slots) assignOptional(output, slot, readOptional(
|
|
787
|
+
for (const slot of slots) assignOptional(output, slot, readOptional(record3, slot, parseTextureBinding, path));
|
|
778
788
|
return output;
|
|
779
789
|
}
|
|
780
790
|
function parseMaterial(value, path, budget) {
|
|
@@ -800,88 +810,88 @@ function parseMaterial(value, path, budget) {
|
|
|
800
810
|
"extensions",
|
|
801
811
|
"sourceMetadata"
|
|
802
812
|
];
|
|
803
|
-
const
|
|
804
|
-
const alphaMode = readEnum(requireKey(
|
|
805
|
-
const alphaCutoff = readOptional(
|
|
813
|
+
const record3 = readRecord(value, path, keys);
|
|
814
|
+
const alphaMode = readEnum(requireKey(record3, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
|
|
815
|
+
const alphaCutoff = readOptional(record3, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
|
|
806
816
|
if (alphaMode === "mask" && alphaCutoff === void 0) return fail("invalid-value", `${path}.alphaCutoff`, "mask materials require alphaCutoff");
|
|
807
817
|
if (alphaMode !== "mask" && alphaCutoff !== void 0) return fail("invalid-value", `${path}.alphaCutoff`, "alphaCutoff is only valid for mask materials");
|
|
808
818
|
const output = {
|
|
809
|
-
id: readId(requireKey(
|
|
810
|
-
workflow: readEnum(requireKey(
|
|
819
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
820
|
+
workflow: readEnum(requireKey(record3, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
|
|
811
821
|
alphaMode,
|
|
812
|
-
baseColorFactor: readNumberTuple(requireKey(
|
|
813
|
-
textures: parseMaterialTextures(requireKey(
|
|
814
|
-
extensions: readMetadata(requireKey(
|
|
815
|
-
sourceMetadata: readMetadata(requireKey(
|
|
822
|
+
baseColorFactor: readNumberTuple(requireKey(record3, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
|
|
823
|
+
textures: parseMaterialTextures(requireKey(record3, "textures", path), `${path}.textures`),
|
|
824
|
+
extensions: readMetadata(requireKey(record3, "extensions", path), `${path}.extensions`, budget),
|
|
825
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
816
826
|
};
|
|
817
|
-
assignOptional(output, "name", readOptional(
|
|
827
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
818
828
|
assignOptional(output, "alphaCutoff", alphaCutoff);
|
|
819
|
-
assignOptional(output, "doubleSided", readOptional(
|
|
829
|
+
assignOptional(output, "doubleSided", readOptional(record3, "doubleSided", readBoolean, path));
|
|
820
830
|
for (const key of ["metallicFactor", "roughnessFactor", "transmissionFactor", "clearcoatFactor", "clearcoatRoughnessFactor", "sheenRoughnessFactor", "occlusionStrength"]) {
|
|
821
|
-
assignOptional(output, key, readOptional(
|
|
831
|
+
assignOptional(output, key, readOptional(record3, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
822
832
|
}
|
|
823
|
-
assignOptional(output, "normalScale", readOptional(
|
|
824
|
-
assignOptional(output, "emissiveFactor", readOptional(
|
|
825
|
-
assignOptional(output, "sheenColorFactor", readOptional(
|
|
833
|
+
assignOptional(output, "normalScale", readOptional(record3, "normalScale", readFinite, path));
|
|
834
|
+
assignOptional(output, "emissiveFactor", readOptional(record3, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
|
|
835
|
+
assignOptional(output, "sheenColorFactor", readOptional(record3, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
|
|
826
836
|
return output;
|
|
827
837
|
}
|
|
828
838
|
function parseTextureSampler(value, path) {
|
|
829
|
-
const
|
|
830
|
-
const output = { id: readId(requireKey(
|
|
831
|
-
assignOptional(output, "wrapS", readOptional(
|
|
832
|
-
assignOptional(output, "wrapT", readOptional(
|
|
833
|
-
assignOptional(output, "minFilter", readOptional(
|
|
834
|
-
assignOptional(output, "magFilter", readOptional(
|
|
835
|
-
assignOptional(output, "mipFilter", readOptional(
|
|
839
|
+
const record3 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
|
|
840
|
+
const output = { id: readId(requireKey(record3, "id", path), `${path}.id`) };
|
|
841
|
+
assignOptional(output, "wrapS", readOptional(record3, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
842
|
+
assignOptional(output, "wrapT", readOptional(record3, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
843
|
+
assignOptional(output, "minFilter", readOptional(record3, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
844
|
+
assignOptional(output, "magFilter", readOptional(record3, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
845
|
+
assignOptional(output, "mipFilter", readOptional(record3, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
|
|
836
846
|
return output;
|
|
837
847
|
}
|
|
838
848
|
function parseTexture(value, path, budget) {
|
|
839
|
-
const
|
|
840
|
-
const usages = Object.freeze(readArray(requireKey(
|
|
849
|
+
const record3 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
|
|
850
|
+
const usages = Object.freeze(readArray(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
|
|
841
851
|
ensureUnique(usages, `${path}.intendedUsage`, "duplicate intended texture usage");
|
|
842
852
|
const output = {
|
|
843
|
-
id: readId(requireKey(
|
|
844
|
-
contentHash: readSha256(requireKey(
|
|
845
|
-
imageResourceId: readId(requireKey(
|
|
853
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
854
|
+
contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
|
|
855
|
+
imageResourceId: readId(requireKey(record3, "imageResourceId", path), `${path}.imageResourceId`),
|
|
846
856
|
intendedUsage: usages,
|
|
847
|
-
colorSpace: readEnum(requireKey(
|
|
848
|
-
exportEmbeddingPolicy: readEnum(requireKey(
|
|
849
|
-
sourceMetadata: readMetadata(requireKey(
|
|
857
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
858
|
+
exportEmbeddingPolicy: readEnum(requireKey(record3, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
|
|
859
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
850
860
|
};
|
|
851
|
-
assignOptional(output, "fileName", readOptional(
|
|
852
|
-
assignOptional(output, "relativePath", readOptional(
|
|
853
|
-
assignOptional(output, "mimeType", readOptional(
|
|
854
|
-
assignOptional(output, "byteLength", readOptional(
|
|
855
|
-
assignOptional(output, "sampler", readOptional(
|
|
861
|
+
assignOptional(output, "fileName", readOptional(record3, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
|
|
862
|
+
assignOptional(output, "relativePath", readOptional(record3, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
|
|
863
|
+
assignOptional(output, "mimeType", readOptional(record3, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
|
|
864
|
+
assignOptional(output, "byteLength", readOptional(record3, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
865
|
+
assignOptional(output, "sampler", readOptional(record3, "sampler", parseTextureSampler, path));
|
|
856
866
|
return output;
|
|
857
867
|
}
|
|
858
868
|
function parseSkeleton(value, path, budget) {
|
|
859
|
-
const
|
|
860
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
861
|
-
const rootJointIds = Object.freeze(readArray(requireKey(
|
|
869
|
+
const record3 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
|
|
870
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
871
|
+
const rootJointIds = Object.freeze(readArray(requireKey(record3, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
|
|
862
872
|
consumeBudget(budget, "skinJoints", jointIds.length + rootJointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "rig joint");
|
|
863
873
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skeleton joint");
|
|
864
874
|
ensureUnique(rootJointIds, `${path}.rootJointIds`, "duplicate root joint");
|
|
865
875
|
const output = {
|
|
866
|
-
id: readId(requireKey(
|
|
876
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
867
877
|
jointIds,
|
|
868
878
|
rootJointIds,
|
|
869
|
-
sourceMetadata: readMetadata(requireKey(
|
|
879
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
870
880
|
};
|
|
871
|
-
assignOptional(output, "name", readOptional(
|
|
881
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
872
882
|
return output;
|
|
873
883
|
}
|
|
874
884
|
function parseJoint(value, path, budget) {
|
|
875
|
-
const
|
|
885
|
+
const record3 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
|
|
876
886
|
const output = {
|
|
877
|
-
id: readId(requireKey(
|
|
878
|
-
nodeId: readId(requireKey(
|
|
879
|
-
restLocalTransform: parseTransformTrs(requireKey(
|
|
880
|
-
sourceMetadata: readMetadata(requireKey(
|
|
887
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
888
|
+
nodeId: readId(requireKey(record3, "nodeId", path), `${path}.nodeId`),
|
|
889
|
+
restLocalTransform: parseTransformTrs(requireKey(record3, "restLocalTransform", path), `${path}.restLocalTransform`),
|
|
890
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
881
891
|
};
|
|
882
|
-
assignOptional(output, "name", readOptional(
|
|
883
|
-
assignOptional(output, "parentJointId", readOptional(
|
|
884
|
-
assignOptional(output, "inverseBindMatrix", readOptional(
|
|
892
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
893
|
+
assignOptional(output, "parentJointId", readOptional(record3, "parentJointId", readId, path));
|
|
894
|
+
assignOptional(output, "inverseBindMatrix", readOptional(record3, "inverseBindMatrix", parseMatrix, path));
|
|
885
895
|
return output;
|
|
886
896
|
}
|
|
887
897
|
function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
@@ -895,43 +905,43 @@ function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
|
895
905
|
}));
|
|
896
906
|
}
|
|
897
907
|
function parseVertexWeightSet(value, path, budget) {
|
|
898
|
-
const
|
|
899
|
-
const influences = readSafeInteger(requireKey(
|
|
908
|
+
const record3 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
|
|
909
|
+
const influences = readSafeInteger(requireKey(record3, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
|
|
900
910
|
if (influences % 4 !== 0) return fail("invalid-value", `${path}.influencesPerVertex`, "influencesPerVertex must be a multiple of four");
|
|
901
|
-
const jointRows = readArray(requireKey(
|
|
902
|
-
const weightRows = readArray(requireKey(
|
|
911
|
+
const jointRows = readArray(requireKey(record3, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
912
|
+
const weightRows = readArray(requireKey(record3, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
903
913
|
if (jointRows.length !== weightRows.length) return fail("invalid-value", path, "joint and weight row counts must match");
|
|
904
914
|
return {
|
|
905
|
-
primitiveId: readId(requireKey(
|
|
915
|
+
primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`),
|
|
906
916
|
influencesPerVertex: influences,
|
|
907
917
|
joints: parseWeightRows(jointRows, `${path}.joints`, jointRows.length, influences, true, budget),
|
|
908
918
|
weights: parseWeightRows(weightRows, `${path}.weights`, weightRows.length, influences, false, budget)
|
|
909
919
|
};
|
|
910
920
|
}
|
|
911
921
|
function parseSkin(value, path, budget) {
|
|
912
|
-
const
|
|
913
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
922
|
+
const record3 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
|
|
923
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
914
924
|
consumeBudget(budget, "skinJoints", jointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "skin joint");
|
|
915
925
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skin joint");
|
|
916
|
-
const inverseInput = readArray(requireKey(
|
|
926
|
+
const inverseInput = readArray(requireKey(record3, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
|
|
917
927
|
if (inverseInput.length !== jointIds.length) return fail("invalid-value", `${path}.inverseBindMatrices`, "one inverse bind matrix is required per skin joint");
|
|
918
|
-
const weightInput = readArray(requireKey(
|
|
928
|
+
const weightInput = readArray(requireKey(record3, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
919
929
|
const weightSets = Object.freeze(weightInput.map((entry, index) => parseVertexWeightSet(entry, `${path}.weightSets[${index}]`, budget)));
|
|
920
930
|
ensureUnique(weightSets.map(({ primitiveId }) => primitiveId), `${path}.weightSets`, "duplicate primitive weight set");
|
|
921
931
|
return {
|
|
922
|
-
id: readId(requireKey(
|
|
923
|
-
skeletonId: readId(requireKey(
|
|
932
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
933
|
+
skeletonId: readId(requireKey(record3, "skeletonId", path), `${path}.skeletonId`),
|
|
924
934
|
jointIds,
|
|
925
935
|
inverseBindMatrices: Object.freeze(inverseInput.map((entry, index) => parseMatrix(entry, `${path}.inverseBindMatrices[${index}]`))),
|
|
926
936
|
weightSets,
|
|
927
|
-
sourceMetadata: readMetadata(requireKey(
|
|
937
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
928
938
|
};
|
|
929
939
|
}
|
|
930
940
|
function parseBlendShapeDelta(value, path) {
|
|
931
|
-
const
|
|
932
|
-
const output = { primitiveId: readId(requireKey(
|
|
941
|
+
const record3 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
|
|
942
|
+
const output = { primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`) };
|
|
933
943
|
for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
|
|
934
|
-
assignOptional(output, key, readOptional(
|
|
944
|
+
assignOptional(output, key, readOptional(record3, key, readId, path));
|
|
935
945
|
}
|
|
936
946
|
if (!output.positionAccessorId && !output.normalAccessorId && !output.tangentAccessorId) {
|
|
937
947
|
return fail("invalid-value", path, "blend-shape delta requires at least one accessor");
|
|
@@ -939,8 +949,8 @@ function parseBlendShapeDelta(value, path) {
|
|
|
939
949
|
return output;
|
|
940
950
|
}
|
|
941
951
|
function parseBlendShape(value, path, budget) {
|
|
942
|
-
const
|
|
943
|
-
const deltaInput = readArray(requireKey(
|
|
952
|
+
const record3 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
|
|
953
|
+
const deltaInput = readArray(requireKey(record3, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
944
954
|
consumeBudget(
|
|
945
955
|
budget,
|
|
946
956
|
"blendShapeDeltas",
|
|
@@ -952,96 +962,96 @@ function parseBlendShape(value, path, budget) {
|
|
|
952
962
|
const deltas = Object.freeze(deltaInput.map((entry, index) => parseBlendShapeDelta(entry, `${path}.deltas[${index}]`)));
|
|
953
963
|
ensureUnique(deltas.map(({ primitiveId }) => primitiveId), `${path}.deltas`, "duplicate blend-shape primitive delta");
|
|
954
964
|
const output = {
|
|
955
|
-
id: readId(requireKey(
|
|
956
|
-
meshId: readId(requireKey(
|
|
965
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
966
|
+
meshId: readId(requireKey(record3, "meshId", path), `${path}.meshId`),
|
|
957
967
|
deltas,
|
|
958
|
-
sourceMetadata: readMetadata(requireKey(
|
|
968
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
959
969
|
};
|
|
960
|
-
assignOptional(output, "name", readOptional(
|
|
961
|
-
assignOptional(output, "defaultWeight", readOptional(
|
|
970
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
971
|
+
assignOptional(output, "defaultWeight", readOptional(record3, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
962
972
|
return output;
|
|
963
973
|
}
|
|
964
974
|
function parseAnimationSampler(value, path, budget) {
|
|
965
|
-
const
|
|
966
|
-
const timesInput = readArray(requireKey(
|
|
967
|
-
const valuesInput = readArray(requireKey(
|
|
975
|
+
const record3 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
|
|
976
|
+
const timesInput = readArray(requireKey(record3, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
977
|
+
const valuesInput = readArray(requireKey(record3, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
968
978
|
consumeBudget(budget, "animationValues", timesInput.length + valuesInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationValues, path, "animation value");
|
|
969
979
|
const times = Object.freeze(timesInput.map((entry, index) => readFinite(entry, `${path}.inputTimesSeconds[${index}]`, 0)));
|
|
970
980
|
for (let index = 1; index < times.length; index += 1) {
|
|
971
981
|
if (times[index] <= times[index - 1]) return fail("invalid-value", `${path}.inputTimesSeconds`, "animation input times must be strictly increasing");
|
|
972
982
|
}
|
|
973
983
|
return {
|
|
974
|
-
id: readId(requireKey(
|
|
975
|
-
inputAccessorId: readId(requireKey(
|
|
976
|
-
outputAccessorId: readId(requireKey(
|
|
984
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
985
|
+
inputAccessorId: readId(requireKey(record3, "inputAccessorId", path), `${path}.inputAccessorId`),
|
|
986
|
+
outputAccessorId: readId(requireKey(record3, "outputAccessorId", path), `${path}.outputAccessorId`),
|
|
977
987
|
inputTimesSeconds: times,
|
|
978
988
|
outputValues: Object.freeze(valuesInput.map((entry, index) => readFinite(entry, `${path}.outputValues[${index}]`))),
|
|
979
|
-
interpolation: readEnum(requireKey(
|
|
980
|
-
sourceMetadata: readMetadata(requireKey(
|
|
989
|
+
interpolation: readEnum(requireKey(record3, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
|
|
990
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
981
991
|
};
|
|
982
992
|
}
|
|
983
993
|
function parseAnimationChannel(value, path, budget) {
|
|
984
|
-
const
|
|
994
|
+
const record3 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
|
|
985
995
|
return {
|
|
986
|
-
id: readId(requireKey(
|
|
987
|
-
samplerId: readId(requireKey(
|
|
988
|
-
targetType: readEnum(requireKey(
|
|
989
|
-
targetId: readId(requireKey(
|
|
990
|
-
sourceMetadata: readMetadata(requireKey(
|
|
996
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
997
|
+
samplerId: readId(requireKey(record3, "samplerId", path), `${path}.samplerId`),
|
|
998
|
+
targetType: readEnum(requireKey(record3, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
|
|
999
|
+
targetId: readId(requireKey(record3, "targetId", path), `${path}.targetId`),
|
|
1000
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
991
1001
|
};
|
|
992
1002
|
}
|
|
993
1003
|
function parseAnimation(value, path, budget) {
|
|
994
|
-
const
|
|
995
|
-
const samplerInput = readArray(requireKey(
|
|
996
|
-
const channelInput = readArray(requireKey(
|
|
1004
|
+
const record3 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
|
|
1005
|
+
const samplerInput = readArray(requireKey(record3, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
|
|
1006
|
+
const channelInput = readArray(requireKey(record3, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
|
|
997
1007
|
consumeBudget(budget, "animationSamplers", samplerInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, path, "animation sampler");
|
|
998
1008
|
consumeBudget(budget, "animationChannels", channelInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, path, "animation channel");
|
|
999
1009
|
const output = {
|
|
1000
|
-
id: readId(requireKey(
|
|
1001
|
-
durationSeconds: readFinite(requireKey(
|
|
1010
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
1011
|
+
durationSeconds: readFinite(requireKey(record3, "durationSeconds", path), `${path}.durationSeconds`, 0),
|
|
1002
1012
|
samplers: Object.freeze(samplerInput.map((entry, index) => parseAnimationSampler(entry, `${path}.samplers[${index}]`, budget))),
|
|
1003
1013
|
channels: Object.freeze(channelInput.map((entry, index) => parseAnimationChannel(entry, `${path}.channels[${index}]`, budget))),
|
|
1004
|
-
sourceMetadata: readMetadata(requireKey(
|
|
1014
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
1005
1015
|
};
|
|
1006
|
-
assignOptional(output, "name", readOptional(
|
|
1016
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
1007
1017
|
return output;
|
|
1008
1018
|
}
|
|
1009
1019
|
function parseAnalyticGeometry(value, path, budget) {
|
|
1010
|
-
const
|
|
1011
|
-
if (requireKey(
|
|
1020
|
+
const record3 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
|
|
1021
|
+
if (requireKey(record3, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
|
|
1012
1022
|
const output = {
|
|
1013
|
-
id: readId(requireKey(
|
|
1023
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
1014
1024
|
kind: "brep-placeholder",
|
|
1015
|
-
sourceResourceId: readId(requireKey(
|
|
1016
|
-
sourceUnit: readEnum(requireKey(
|
|
1025
|
+
sourceResourceId: readId(requireKey(record3, "sourceResourceId", path), `${path}.sourceResourceId`),
|
|
1026
|
+
sourceUnit: readEnum(requireKey(record3, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
|
|
1017
1027
|
};
|
|
1018
|
-
assignOptional(output, "tessellatedMeshId", readOptional(
|
|
1019
|
-
assignOptional(output, "metadata", readOptional(
|
|
1028
|
+
assignOptional(output, "tessellatedMeshId", readOptional(record3, "tessellatedMeshId", readId, path));
|
|
1029
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1020
1030
|
return output;
|
|
1021
1031
|
}
|
|
1022
1032
|
function parseProvenance(value, budget) {
|
|
1023
1033
|
const path = "$.provenance";
|
|
1024
|
-
const
|
|
1034
|
+
const record3 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
|
|
1025
1035
|
const output = {
|
|
1026
|
-
sourceFormat: readString(requireKey(
|
|
1027
|
-
sourceContentHash: readSha256(requireKey(
|
|
1028
|
-
converterId: readString(requireKey(
|
|
1029
|
-
converterVersion: readString(requireKey(
|
|
1036
|
+
sourceFormat: readString(requireKey(record3, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
|
|
1037
|
+
sourceContentHash: readSha256(requireKey(record3, "sourceContentHash", path), `${path}.sourceContentHash`),
|
|
1038
|
+
converterId: readString(requireKey(record3, "converterId", path), `${path}.converterId`, 128, TOKEN),
|
|
1039
|
+
converterVersion: readString(requireKey(record3, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
|
|
1030
1040
|
};
|
|
1031
|
-
assignOptional(output, "provider", readOptional(
|
|
1032
|
-
assignOptional(output, "sourceIdentifier", readOptional(
|
|
1033
|
-
assignOptional(output, "metadata", readOptional(
|
|
1041
|
+
assignOptional(output, "provider", readOptional(record3, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
1042
|
+
assignOptional(output, "sourceIdentifier", readOptional(record3, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
|
|
1043
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1034
1044
|
return output;
|
|
1035
1045
|
}
|
|
1036
1046
|
function parseDiagnostic(value, path, budget) {
|
|
1037
|
-
const
|
|
1047
|
+
const record3 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
|
|
1038
1048
|
const output = {
|
|
1039
|
-
severity: readEnum(requireKey(
|
|
1040
|
-
code: readString(requireKey(
|
|
1041
|
-
message: readString(requireKey(
|
|
1049
|
+
severity: readEnum(requireKey(record3, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
|
|
1050
|
+
code: readString(requireKey(record3, "code", path), `${path}.code`, 128, TOKEN),
|
|
1051
|
+
message: readString(requireKey(record3, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
|
|
1042
1052
|
};
|
|
1043
|
-
assignOptional(output, "path", readOptional(
|
|
1044
|
-
assignOptional(output, "metadata", readOptional(
|
|
1053
|
+
assignOptional(output, "path", readOptional(record3, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
|
|
1054
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1045
1055
|
return output;
|
|
1046
1056
|
}
|
|
1047
1057
|
function ensureUnique(values, path, detail = "duplicate identity") {
|
|
@@ -1682,11 +1692,11 @@ var DOCUMENT_KEYS = Object.freeze([
|
|
|
1682
1692
|
]);
|
|
1683
1693
|
function parseGpuModelDocument(value) {
|
|
1684
1694
|
const path = "$";
|
|
1685
|
-
const
|
|
1686
|
-
const schemaVersion = requireKey(
|
|
1695
|
+
const record3 = readRecord(value, path, DOCUMENT_KEYS);
|
|
1696
|
+
const schemaVersion = requireKey(record3, "schemaVersion", path);
|
|
1687
1697
|
if (schemaVersion !== GPU_MODEL_DOCUMENT_SCHEMA_VERSION) return fail("invalid-value", "$.schemaVersion", "unsupported schema version");
|
|
1688
1698
|
const parseCollection = (key, maximum, parser, minimum = 0) => {
|
|
1689
|
-
const input = readArray(requireKey(
|
|
1699
|
+
const input = readArray(requireKey(record3, key, path), `$.${key}`, maximum, minimum);
|
|
1690
1700
|
return Object.freeze(input.map((entry, index) => parser(entry, `$.${key}[${index}]`)));
|
|
1691
1701
|
};
|
|
1692
1702
|
const budget = {
|
|
@@ -1707,10 +1717,10 @@ function parseGpuModelDocument(value) {
|
|
|
1707
1717
|
geometryWorldAccessorInstances: 0,
|
|
1708
1718
|
geometryWorldVertices: 0
|
|
1709
1719
|
};
|
|
1710
|
-
const roots = Object.freeze(readArray(requireKey(
|
|
1720
|
+
const roots = Object.freeze(readArray(requireKey(record3, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
|
|
1711
1721
|
const document = {
|
|
1712
1722
|
schemaVersion,
|
|
1713
|
-
coordinateSystem: parseCoordinateSystem(requireKey(
|
|
1723
|
+
coordinateSystem: parseCoordinateSystem(requireKey(record3, "coordinateSystem", path)),
|
|
1714
1724
|
roots,
|
|
1715
1725
|
nodes: parseCollection("nodes", GPU_MODEL_DOCUMENT_LIMITS.nodes, (entry, entryPath) => parseNode(entry, entryPath, budget), 1),
|
|
1716
1726
|
resources: parseCollection("resources", GPU_MODEL_DOCUMENT_LIMITS.resources, parseVerifiedResource, 1),
|
|
@@ -1724,10 +1734,10 @@ function parseGpuModelDocument(value) {
|
|
|
1724
1734
|
blendShapes: parseCollection("blendShapes", GPU_MODEL_DOCUMENT_LIMITS.blendShapes, (entry, entryPath) => parseBlendShape(entry, entryPath, budget)),
|
|
1725
1735
|
animations: parseCollection("animations", GPU_MODEL_DOCUMENT_LIMITS.animations, (entry, entryPath) => parseAnimation(entry, entryPath, budget)),
|
|
1726
1736
|
analyticGeometry: parseCollection("analyticGeometry", GPU_MODEL_DOCUMENT_LIMITS.analyticGeometry, (entry, entryPath) => parseAnalyticGeometry(entry, entryPath, budget)),
|
|
1727
|
-
bounds: parseBounds(requireKey(
|
|
1728
|
-
provenance: parseProvenance(requireKey(
|
|
1737
|
+
bounds: parseBounds(requireKey(record3, "bounds", path), "$.bounds"),
|
|
1738
|
+
provenance: parseProvenance(requireKey(record3, "provenance", path), budget),
|
|
1729
1739
|
diagnostics: parseCollection("diagnostics", GPU_MODEL_DOCUMENT_LIMITS.diagnostics, (entry, entryPath) => parseDiagnostic(entry, entryPath, budget)),
|
|
1730
|
-
metadata: readMetadata(requireKey(
|
|
1740
|
+
metadata: readMetadata(requireKey(record3, "metadata", path), "$.metadata", budget)
|
|
1731
1741
|
};
|
|
1732
1742
|
if (document.meshes.length === 0) return fail("invalid-value", "$.meshes", "v1 document bounds require at least one canonical mesh");
|
|
1733
1743
|
validateReferences(document, budget);
|
|
@@ -1764,8 +1774,8 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1764
1774
|
value.resources.forEach((resource, index) => assertVerifiedResourceEvidence(resource, limits, `$.resources[${index}]`));
|
|
1765
1775
|
return value;
|
|
1766
1776
|
}
|
|
1767
|
-
const
|
|
1768
|
-
const resourceInput = readArray(requireKey(
|
|
1777
|
+
const record3 = readRecord(value, "$", DOCUMENT_KEYS);
|
|
1778
|
+
const resourceInput = readArray(requireKey(record3, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
|
|
1769
1779
|
const parsed = resourceInput.map((entry, index) => {
|
|
1770
1780
|
if (typeof entry === "object" && entry !== null && verifiedResources.has(entry) && verifiedResourceBytes.has(entry)) return entry;
|
|
1771
1781
|
return parseResourceHeader(entry, `$.resources[${index}]`);
|
|
@@ -1778,7 +1788,7 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1778
1788
|
}
|
|
1779
1789
|
const candidate = {};
|
|
1780
1790
|
for (const key of DOCUMENT_KEYS) {
|
|
1781
|
-
if (Object.hasOwn(
|
|
1791
|
+
if (Object.hasOwn(record3, key)) candidate[key] = record3[key];
|
|
1782
1792
|
}
|
|
1783
1793
|
candidate.resources = verified;
|
|
1784
1794
|
return createGpuModelDocument(candidate);
|
|
@@ -1843,11 +1853,11 @@ async function hashGpuModelDocumentCanonical(document) {
|
|
|
1843
1853
|
return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
1844
1854
|
}
|
|
1845
1855
|
function resolveStaticDemoLimits(options) {
|
|
1846
|
-
const
|
|
1847
|
-
const enabled = readBoolean(requireKey(
|
|
1856
|
+
const record3 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
|
|
1857
|
+
const enabled = readBoolean(requireKey(record3, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
|
|
1848
1858
|
if (!enabled) return fail("feature-disabled", "$.options.pvoxModelsEnabled", `${GPU_MODEL_STATIC_DEMO_FEATURE_FLAG} is disabled`);
|
|
1849
|
-
if (!Object.hasOwn(
|
|
1850
|
-
const overrides = readRecord(
|
|
1859
|
+
if (!Object.hasOwn(record3, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
|
|
1860
|
+
const overrides = readRecord(record3.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
|
|
1851
1861
|
const limits = { ...GPU_MODEL_STATIC_DEMO_LIMITS };
|
|
1852
1862
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_STATIC_DEMO_LIMITS)) {
|
|
1853
1863
|
if (Object.hasOwn(overrides, key)) limits[key] = readSafeInteger(overrides[key], `$.options.limits.${key}`, 1, ceiling);
|
|
@@ -2082,6 +2092,451 @@ async function createGpuModelStaticDemoCompilerInput(document, options) {
|
|
|
2082
2092
|
});
|
|
2083
2093
|
}
|
|
2084
2094
|
|
|
2095
|
+
// src/diagnostics.ts
|
|
2096
|
+
var GPU_MODEL_REPAIR_RULES = Object.freeze([
|
|
2097
|
+
{
|
|
2098
|
+
"id": "missing-normals",
|
|
2099
|
+
"action": "Generate deterministic vertex normals from validated triangle geometry.",
|
|
2100
|
+
"severity": "warning",
|
|
2101
|
+
"confidence": "high",
|
|
2102
|
+
"standardIds": [
|
|
2103
|
+
"gltf-glb",
|
|
2104
|
+
"obj-mtl",
|
|
2105
|
+
"fbx",
|
|
2106
|
+
"cad-bim"
|
|
2107
|
+
],
|
|
2108
|
+
"policy": "Generate deterministic vertex normals from validated triangle geometry and record the generation basis."
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
"id": "bad-winding",
|
|
2112
|
+
"action": "Normalize winding to the adapter coordinate convention.",
|
|
2113
|
+
"severity": "warning",
|
|
2114
|
+
"confidence": "medium",
|
|
2115
|
+
"standardIds": [
|
|
2116
|
+
"gltf-glb",
|
|
2117
|
+
"obj-mtl",
|
|
2118
|
+
"fbx",
|
|
2119
|
+
"cad-bim"
|
|
2120
|
+
],
|
|
2121
|
+
"policy": "Normalize winding to the adapter coordinate convention and record the inferred orientation."
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
"id": "invalid-indices",
|
|
2125
|
+
"action": "Drop the invalid primitive.",
|
|
2126
|
+
"severity": "error",
|
|
2127
|
+
"confidence": "medium",
|
|
2128
|
+
"standardIds": [
|
|
2129
|
+
"gltf-glb",
|
|
2130
|
+
"obj-mtl",
|
|
2131
|
+
"fbx",
|
|
2132
|
+
"cad-bim"
|
|
2133
|
+
],
|
|
2134
|
+
"policy": "Reject the invalid primitive or clamp only when the fixture and adapter policy explicitly prove a safe repair; retain the original index in the record."
|
|
2135
|
+
},
|
|
2136
|
+
{
|
|
2137
|
+
"id": "nan-transforms",
|
|
2138
|
+
"action": "Replace non-finite transform components with identity components.",
|
|
2139
|
+
"severity": "error",
|
|
2140
|
+
"confidence": "high",
|
|
2141
|
+
"standardIds": [
|
|
2142
|
+
"gltf-glb",
|
|
2143
|
+
"obj-mtl",
|
|
2144
|
+
"fbx",
|
|
2145
|
+
"cad-bim"
|
|
2146
|
+
],
|
|
2147
|
+
"policy": "Replace the non-finite component with the identity transform component and record the source value and fallback."
|
|
2148
|
+
},
|
|
2149
|
+
{
|
|
2150
|
+
"id": "non-normalized-weights",
|
|
2151
|
+
"action": "Normalize finite non-negative skin weights.",
|
|
2152
|
+
"severity": "warning",
|
|
2153
|
+
"confidence": "high",
|
|
2154
|
+
"standardIds": [
|
|
2155
|
+
"gltf-glb",
|
|
2156
|
+
"fbx",
|
|
2157
|
+
"cad-bim"
|
|
2158
|
+
],
|
|
2159
|
+
"policy": "Normalize finite, non-negative influences and record the pre-normalization weights and discarded influence policy."
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
"id": "missing-texture-path",
|
|
2163
|
+
"action": "Leave the missing or unsafe texture slot unresolved.",
|
|
2164
|
+
"severity": "warning",
|
|
2165
|
+
"confidence": "low",
|
|
2166
|
+
"standardIds": [
|
|
2167
|
+
"gltf-glb",
|
|
2168
|
+
"obj-mtl",
|
|
2169
|
+
"fbx",
|
|
2170
|
+
"cad-bim"
|
|
2171
|
+
],
|
|
2172
|
+
"policy": "Resolve only an explicitly bounded package-relative fallback; otherwise keep the material slot unresolved and emit a visible warning."
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
"id": "malformed-material-reference",
|
|
2176
|
+
"action": "Attach the canonical default material.",
|
|
2177
|
+
"severity": "error",
|
|
2178
|
+
"confidence": "medium",
|
|
2179
|
+
"standardIds": [
|
|
2180
|
+
"gltf-glb",
|
|
2181
|
+
"obj-mtl",
|
|
2182
|
+
"fbx",
|
|
2183
|
+
"cad-bim"
|
|
2184
|
+
],
|
|
2185
|
+
"policy": "Attach the canonical default material only when the target policy permits it and preserve the unresolved source reference."
|
|
2186
|
+
}
|
|
2187
|
+
].map((rule) => Object.freeze({ ...rule, standardIds: Object.freeze(rule.standardIds) })));
|
|
2188
|
+
var GPU_MODEL_DIAGNOSTIC_LIMITS = Object.freeze({
|
|
2189
|
+
issues: 4096,
|
|
2190
|
+
values: 16384,
|
|
2191
|
+
depth: 12,
|
|
2192
|
+
members: 1024,
|
|
2193
|
+
string: 65536,
|
|
2194
|
+
bytes: 1048576
|
|
2195
|
+
});
|
|
2196
|
+
var GpuModelDiagnosticsError = class extends TypeError {
|
|
2197
|
+
constructor() {
|
|
2198
|
+
super("Invalid or over-budget GPU model diagnostics report");
|
|
2199
|
+
this.name = "GpuModelDiagnosticsError";
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
function fail2() {
|
|
2203
|
+
throw new GpuModelDiagnosticsError();
|
|
2204
|
+
}
|
|
2205
|
+
function charge(budget, bytes) {
|
|
2206
|
+
budget.bytes += bytes;
|
|
2207
|
+
if (budget.bytes > GPU_MODEL_DIAGNOSTIC_LIMITS.bytes) fail2();
|
|
2208
|
+
}
|
|
2209
|
+
function textBytes(value) {
|
|
2210
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
2211
|
+
}
|
|
2212
|
+
function snapshot(value, depth, budget, ancestors, memberLimit = GPU_MODEL_DIAGNOSTIC_LIMITS.members) {
|
|
2213
|
+
if (++budget.values > GPU_MODEL_DIAGNOSTIC_LIMITS.values || depth > GPU_MODEL_DIAGNOSTIC_LIMITS.depth) fail2();
|
|
2214
|
+
if (value === null || typeof value === "boolean") {
|
|
2215
|
+
charge(budget, value === null ? 4 : value ? 4 : 5);
|
|
2216
|
+
return value;
|
|
2217
|
+
}
|
|
2218
|
+
if (typeof value === "number") {
|
|
2219
|
+
if (!Number.isFinite(value)) fail2();
|
|
2220
|
+
charge(budget, JSON.stringify(value).length);
|
|
2221
|
+
return Object.is(value, -0) ? 0 : value;
|
|
2222
|
+
}
|
|
2223
|
+
if (typeof value === "string") {
|
|
2224
|
+
if (value.length > GPU_MODEL_DIAGNOSTIC_LIMITS.string) fail2();
|
|
2225
|
+
charge(budget, textBytes(value));
|
|
2226
|
+
return value;
|
|
2227
|
+
}
|
|
2228
|
+
if (typeof value !== "object" || ancestors.has(value)) fail2();
|
|
2229
|
+
const array2 = Array.isArray(value);
|
|
2230
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2231
|
+
if (array2 ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
|
|
2232
|
+
const keys = Reflect.ownKeys(value);
|
|
2233
|
+
const maxMembers = memberLimit;
|
|
2234
|
+
if (keys.length > maxMembers + (array2 ? 1 : 0)) fail2();
|
|
2235
|
+
ancestors.add(value);
|
|
2236
|
+
charge(budget, 2);
|
|
2237
|
+
if (array2) {
|
|
2238
|
+
const length = Object.getOwnPropertyDescriptor(value, "length").value;
|
|
2239
|
+
if (length > maxMembers || keys.length !== length + 1) fail2();
|
|
2240
|
+
const result2 = [];
|
|
2241
|
+
for (let i = 0; i < length; i++) {
|
|
2242
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
2243
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail2();
|
|
2244
|
+
if (i > 0) charge(budget, 1);
|
|
2245
|
+
result2.push(snapshot(descriptor.value, depth + 1, budget, ancestors));
|
|
2246
|
+
}
|
|
2247
|
+
ancestors.delete(value);
|
|
2248
|
+
return Object.freeze(result2);
|
|
2249
|
+
}
|
|
2250
|
+
const result = {};
|
|
2251
|
+
let count = 0;
|
|
2252
|
+
for (const key of keys) {
|
|
2253
|
+
if (typeof key !== "string" || key.length > 256 || ["__proto__", "constructor", "prototype"].includes(key)) fail2();
|
|
2254
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2255
|
+
if (!("value" in descriptor) || !descriptor.enumerable) fail2();
|
|
2256
|
+
if (count++ > 0) charge(budget, 1);
|
|
2257
|
+
charge(budget, textBytes(key) + 1);
|
|
2258
|
+
result[key] = snapshot(descriptor.value, depth + 1, budget, ancestors, depth === 0 && key === "issues" ? GPU_MODEL_DIAGNOSTIC_LIMITS.issues : GPU_MODEL_DIAGNOSTIC_LIMITS.members);
|
|
2259
|
+
}
|
|
2260
|
+
ancestors.delete(value);
|
|
2261
|
+
return Object.freeze(result);
|
|
2262
|
+
}
|
|
2263
|
+
function record(value) {
|
|
2264
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return fail2();
|
|
2265
|
+
return value;
|
|
2266
|
+
}
|
|
2267
|
+
function keysOnly(value, allowed) {
|
|
2268
|
+
if (Object.keys(value).some((key) => !allowed.includes(key))) fail2();
|
|
2269
|
+
}
|
|
2270
|
+
function enumValue(value, allowed) {
|
|
2271
|
+
if (typeof value !== "string" || !allowed.includes(value)) return fail2();
|
|
2272
|
+
return value;
|
|
2273
|
+
}
|
|
2274
|
+
function hasUnsafeText(value) {
|
|
2275
|
+
for (const character of value) {
|
|
2276
|
+
const point = character.codePointAt(0);
|
|
2277
|
+
if (point < 32 || point >= 127 && point <= 159 || point >= 8232 && point <= 8238 || point >= 8294 && point <= 8297 || point >= 55296 && point <= 57343) return true;
|
|
2278
|
+
}
|
|
2279
|
+
return false;
|
|
2280
|
+
}
|
|
2281
|
+
function text(value, max) {
|
|
2282
|
+
if (typeof value !== "string" || value.length === 0 || value.length > max || hasUnsafeText(value)) return fail2();
|
|
2283
|
+
return value;
|
|
2284
|
+
}
|
|
2285
|
+
function boolean(value) {
|
|
2286
|
+
if (typeof value !== "boolean") return fail2();
|
|
2287
|
+
return value;
|
|
2288
|
+
}
|
|
2289
|
+
function parseIssue(value) {
|
|
2290
|
+
const issue = record(value);
|
|
2291
|
+
const kind = enumValue(issue.kind, ["violation", "unsupported", "warning", "loss"]);
|
|
2292
|
+
const base = { code: text(issue.code, 128), path: text(issue.path, 1024), message: text(issue.message, 2048) };
|
|
2293
|
+
if (!base.path.startsWith("/")) fail2();
|
|
2294
|
+
const common = ["kind", "code", "path", "message"];
|
|
2295
|
+
if (kind === "violation") {
|
|
2296
|
+
keysOnly(issue, [...common, "ruleId", "originalValue", "repairApplied"]);
|
|
2297
|
+
if (!Object.hasOwn(issue, "originalValue")) fail2();
|
|
2298
|
+
return { ...base, kind, ruleId: text(issue.ruleId, 128), originalValue: issue.originalValue, repairApplied: boolean(issue.repairApplied) };
|
|
2299
|
+
}
|
|
2300
|
+
if (kind === "loss") {
|
|
2301
|
+
keysOnly(issue, [...common, "effect"]);
|
|
2302
|
+
return { ...base, kind, effect: enumValue(issue.effect, ["dropped", "approximated"]) };
|
|
2303
|
+
}
|
|
2304
|
+
keysOnly(issue, common);
|
|
2305
|
+
return { ...base, kind };
|
|
2306
|
+
}
|
|
2307
|
+
function evaluateGpuModelDiagnostics(value) {
|
|
2308
|
+
const budget = { values: 0, bytes: 0 };
|
|
2309
|
+
const args = record(snapshot(value, 0, budget, /* @__PURE__ */ new Set()));
|
|
2310
|
+
keysOnly(args, ["standard", "mode", "allowLoss", "issues", "rawSource", "maxEvidenceBytes"]);
|
|
2311
|
+
const standard = enumValue(args.standard, ["gltf-glb", "obj-mtl", "fbx", "cad-bim"]);
|
|
2312
|
+
const mode = args.mode === void 0 ? "strict" : enumValue(args.mode, ["strict", "tolerant", "forensic"]);
|
|
2313
|
+
const allowLoss = args.allowLoss === void 0 ? false : boolean(args.allowLoss);
|
|
2314
|
+
if (args.maxEvidenceBytes !== void 0 && (typeof args.maxEvidenceBytes !== "number" || !Number.isSafeInteger(args.maxEvidenceBytes) || args.maxEvidenceBytes < 1 || args.maxEvidenceBytes > GPU_MODEL_DIAGNOSTIC_LIMITS.bytes || budget.bytes > args.maxEvidenceBytes)) fail2();
|
|
2315
|
+
if (mode === "forensic" ? !Object.hasOwn(args, "rawSource") : Object.hasOwn(args, "rawSource")) fail2();
|
|
2316
|
+
if (!Array.isArray(args.issues) || args.issues.length > GPU_MODEL_DIAGNOSTIC_LIMITS.issues) fail2();
|
|
2317
|
+
const issues = args.issues.map(parseIssue);
|
|
2318
|
+
const lossPaths = new Set(issues.filter((issue) => issue.kind === "loss").map((issue) => issue.path));
|
|
2319
|
+
const diagnostics = [];
|
|
2320
|
+
const repairs = [];
|
|
2321
|
+
const losses = [];
|
|
2322
|
+
let accepted = true;
|
|
2323
|
+
for (const issue of issues) {
|
|
2324
|
+
const base = { code: issue.code, path: issue.path, message: issue.message };
|
|
2325
|
+
if (issue.kind === "violation") {
|
|
2326
|
+
const rule = GPU_MODEL_REPAIR_RULES.find((rule2) => rule2.id === issue.ruleId);
|
|
2327
|
+
const requiresLoss = ["invalid-indices", "missing-texture-path", "malformed-material-reference"].includes(issue.ruleId);
|
|
2328
|
+
const repaired = mode !== "strict" && issue.repairApplied && rule && rule.standardIds.includes(standard) && (!requiresLoss || lossPaths.has(issue.path));
|
|
2329
|
+
if (repaired) {
|
|
2330
|
+
repairs.push(Object.freeze({ ruleId: rule.id, originalValue: issue.originalValue, action: rule.action, severity: rule.severity, affectedPath: issue.path, confidence: rule.confidence }));
|
|
2331
|
+
diagnostics.push(Object.freeze({ ...base, kind: "repair", severity: rule.severity, ruleId: rule.id }));
|
|
2332
|
+
} else {
|
|
2333
|
+
accepted = false;
|
|
2334
|
+
diagnostics.push(Object.freeze({ ...base, kind: "blocking-error", severity: "error", ruleId: issue.ruleId }));
|
|
2335
|
+
}
|
|
2336
|
+
} else if (issue.kind === "unsupported") {
|
|
2337
|
+
accepted = false;
|
|
2338
|
+
diagnostics.push(Object.freeze({ ...issue, severity: "error" }));
|
|
2339
|
+
} else if (issue.kind === "loss") {
|
|
2340
|
+
if (!allowLoss) accepted = false;
|
|
2341
|
+
const loss = Object.freeze({ ...issue, severity: "warning" });
|
|
2342
|
+
losses.push(loss);
|
|
2343
|
+
diagnostics.push(loss);
|
|
2344
|
+
} else diagnostics.push(Object.freeze({ ...issue, severity: "warning" }));
|
|
2345
|
+
}
|
|
2346
|
+
return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
// src/resource-graph.ts
|
|
2350
|
+
var GPU_MODEL_RESOURCE_GRAPH_LIMITS = Object.freeze({
|
|
2351
|
+
nodes: 65536,
|
|
2352
|
+
edges: 262144,
|
|
2353
|
+
packages: 256,
|
|
2354
|
+
pathLength: 1024
|
|
2355
|
+
});
|
|
2356
|
+
var GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION = "plasius.gpu-model-resource-graph/1";
|
|
2357
|
+
var KINDS = ["resource", "accessor", "texture", "material", "skeleton", "skin", "animation", "provenance", "package"];
|
|
2358
|
+
var GpuModelResourceGraphError = class extends TypeError {
|
|
2359
|
+
constructor(code) {
|
|
2360
|
+
super(`Invalid GPU model resource graph: ${code}`);
|
|
2361
|
+
this.code = code;
|
|
2362
|
+
this.name = "GpuModelResourceGraphError";
|
|
2363
|
+
}
|
|
2364
|
+
code;
|
|
2365
|
+
};
|
|
2366
|
+
function fail3(code = "invalid-value") {
|
|
2367
|
+
throw new GpuModelResourceGraphError(code);
|
|
2368
|
+
}
|
|
2369
|
+
function identifier(value) {
|
|
2370
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail3();
|
|
2371
|
+
return value;
|
|
2372
|
+
}
|
|
2373
|
+
function choice(value, choices) {
|
|
2374
|
+
if (typeof value !== "string" || !choices.includes(value)) fail3();
|
|
2375
|
+
return value;
|
|
2376
|
+
}
|
|
2377
|
+
function gpuModelResourceKey(kind, id) {
|
|
2378
|
+
return JSON.stringify([choice(kind, KINDS), identifier(id)]);
|
|
2379
|
+
}
|
|
2380
|
+
function record2(value, keys) {
|
|
2381
|
+
if (typeof value !== "object" || value === null || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
2382
|
+
const names = Reflect.ownKeys(value);
|
|
2383
|
+
if (names.length !== keys.length || names.some((key) => typeof key !== "string" || !keys.includes(key))) fail3();
|
|
2384
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2385
|
+
for (const key of keys) {
|
|
2386
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2387
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2388
|
+
result[key] = descriptor.value;
|
|
2389
|
+
}
|
|
2390
|
+
return result;
|
|
2391
|
+
}
|
|
2392
|
+
function array(value, maximum) {
|
|
2393
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail3();
|
|
2394
|
+
if (value.length > maximum) fail3("limit-exceeded");
|
|
2395
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
2396
|
+
const output = [];
|
|
2397
|
+
for (let index = 0; index < value.length; index++) {
|
|
2398
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
2399
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2400
|
+
output.push(descriptor.value);
|
|
2401
|
+
}
|
|
2402
|
+
return output;
|
|
2403
|
+
}
|
|
2404
|
+
function relativePath(value) {
|
|
2405
|
+
if (typeof value !== "string" || value.length > GPU_MODEL_RESOURCE_GRAPH_LIMITS.pathLength || !/^[A-Za-z0-9._/-]+$/u.test(value)) fail3();
|
|
2406
|
+
for (const part of value.split("/")) {
|
|
2407
|
+
if (!part || part.length > 255 || part === "." || part === ".." || part.endsWith(".") || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part)) fail3();
|
|
2408
|
+
}
|
|
2409
|
+
return value;
|
|
2410
|
+
}
|
|
2411
|
+
function parseLocation(value) {
|
|
2412
|
+
if (typeof value !== "object" || value === null) fail3();
|
|
2413
|
+
const kind = Object.getOwnPropertyDescriptor(value, "kind");
|
|
2414
|
+
if (!kind || !("value" in kind)) fail3();
|
|
2415
|
+
if (kind.value === "embedded") {
|
|
2416
|
+
record2(value, ["kind"]);
|
|
2417
|
+
return Object.freeze({ kind: "embedded" });
|
|
2418
|
+
}
|
|
2419
|
+
const location = record2(value, ["kind", "relativePath"]);
|
|
2420
|
+
if (location.kind !== "external") fail3();
|
|
2421
|
+
return Object.freeze({ kind: "external", relativePath: relativePath(location.relativePath) });
|
|
2422
|
+
}
|
|
2423
|
+
var graphs = /* @__PURE__ */ new WeakSet();
|
|
2424
|
+
function isGpuModelResourceGraph(value) {
|
|
2425
|
+
return typeof value === "object" && value !== null && graphs.has(value);
|
|
2426
|
+
}
|
|
2427
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2428
|
+
function createGpuModelResourceGraph(document, input) {
|
|
2429
|
+
try {
|
|
2430
|
+
return buildGraph(document, input);
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
if (error instanceof GpuModelResourceGraphError) throw error;
|
|
2433
|
+
fail3();
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
function buildGraph(document, input) {
|
|
2437
|
+
if (!isGpuModelDocument(document)) fail3("unverified-document");
|
|
2438
|
+
const raw = record2(input, ["resources", "packages"]);
|
|
2439
|
+
const resourceInputs = array(raw.resources, GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes);
|
|
2440
|
+
const packageInputs = array(raw.packages, GPU_MODEL_RESOURCE_GRAPH_LIMITS.packages);
|
|
2441
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2442
|
+
let edgeCount = 0;
|
|
2443
|
+
const add = (kind, id, value, dependencies) => {
|
|
2444
|
+
const key = gpuModelResourceKey(kind, id);
|
|
2445
|
+
if (nodes.has(key)) fail3("duplicate-reference");
|
|
2446
|
+
if (nodes.size >= GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes) fail3("limit-exceeded");
|
|
2447
|
+
edgeCount += dependencies.length;
|
|
2448
|
+
if (edgeCount > GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges) fail3("limit-exceeded");
|
|
2449
|
+
nodes.set(key, Object.freeze({ kind, id, key, value, dependencies: Object.freeze([...new Set(dependencies)].sort(compare)) }));
|
|
2450
|
+
};
|
|
2451
|
+
const resources = new Map(document.resources.map((resource) => [resource.id, resource]));
|
|
2452
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2453
|
+
for (const entry of resourceInputs) {
|
|
2454
|
+
const binding = record2(entry, ["resourceId", "location", "exportEmbeddingPolicy"]);
|
|
2455
|
+
const id = identifier(binding.resourceId);
|
|
2456
|
+
const resource = resources.get(id);
|
|
2457
|
+
if (!resource) fail3("missing-reference");
|
|
2458
|
+
const location = parseLocation(binding.location);
|
|
2459
|
+
const exportEmbeddingPolicy = choice(binding.exportEmbeddingPolicy, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]);
|
|
2460
|
+
if (location.kind === "embedded" && exportEmbeddingPolicy === "forbid-embedding") fail3();
|
|
2461
|
+
if (location.kind === "external") {
|
|
2462
|
+
const path = location.relativePath.toLowerCase();
|
|
2463
|
+
if (paths.has(path)) fail3("duplicate-reference");
|
|
2464
|
+
paths.add(path);
|
|
2465
|
+
}
|
|
2466
|
+
add("resource", id, Object.freeze({ resource, location, exportEmbeddingPolicy }), []);
|
|
2467
|
+
}
|
|
2468
|
+
for (const path of paths) {
|
|
2469
|
+
for (let index = path.indexOf("/"); index !== -1; index = path.indexOf("/", index + 1)) {
|
|
2470
|
+
if (paths.has(path.slice(0, index))) fail3("duplicate-reference");
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
if (nodes.size !== resources.size) fail3("missing-reference");
|
|
2474
|
+
addDocumentNodes(document, add);
|
|
2475
|
+
for (const entry of packageInputs) {
|
|
2476
|
+
const pkg = record2(entry, ["id", "members"]);
|
|
2477
|
+
const id = identifier(pkg.id);
|
|
2478
|
+
const members = [];
|
|
2479
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2480
|
+
for (const entry2 of array(pkg.members, GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges - edgeCount)) {
|
|
2481
|
+
const reference = record2(entry2, ["kind", "id"]);
|
|
2482
|
+
const member = Object.freeze({ kind: choice(reference.kind, KINDS), id: identifier(reference.id) });
|
|
2483
|
+
const key = gpuModelResourceKey(member.kind, member.id);
|
|
2484
|
+
if (dependencies.has(key)) fail3("duplicate-reference");
|
|
2485
|
+
dependencies.add(key);
|
|
2486
|
+
members.push(member);
|
|
2487
|
+
}
|
|
2488
|
+
members.sort((a, b) => compare(gpuModelResourceKey(a.kind, a.id), gpuModelResourceKey(b.kind, b.id)));
|
|
2489
|
+
add("package", id, Object.freeze({ id, members: Object.freeze(members) }), [...dependencies]);
|
|
2490
|
+
}
|
|
2491
|
+
validateDag(nodes);
|
|
2492
|
+
const result = Object.freeze({ schemaVersion: GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION, nodes: Object.freeze([...nodes.values()].sort((a, b) => compare(a.key, b.key))) });
|
|
2493
|
+
graphs.add(result);
|
|
2494
|
+
return result;
|
|
2495
|
+
}
|
|
2496
|
+
function addDocumentNodes(document, add) {
|
|
2497
|
+
const key = gpuModelResourceKey;
|
|
2498
|
+
for (const value of document.accessors) add("accessor", value.id, value, [key("resource", value.resourceId)]);
|
|
2499
|
+
for (const value of document.textures) add("texture", value.id, value, [key("resource", value.imageResourceId)]);
|
|
2500
|
+
for (const value of document.materials) add("material", value.id, value, Object.values(value.textures).map((binding) => key("texture", binding.textureId)));
|
|
2501
|
+
const joints = new Map(document.joints.map((joint) => [joint.id, joint]));
|
|
2502
|
+
for (const skeleton of document.skeletons) add("skeleton", skeleton.id, Object.freeze({ skeleton, joints: Object.freeze(skeleton.jointIds.map((id) => joints.get(id))) }), []);
|
|
2503
|
+
const primitives = new Map(document.meshes.flatMap((mesh) => mesh.primitives.map((primitive) => [primitive.id, primitive])));
|
|
2504
|
+
for (const value of document.skins) {
|
|
2505
|
+
const dependencies = [key("skeleton", value.skeletonId)];
|
|
2506
|
+
for (const weightSet of value.weightSets) {
|
|
2507
|
+
for (const attribute of primitives.get(weightSet.primitiveId).attributes) {
|
|
2508
|
+
if (/^(JOINTS|WEIGHTS)_/u.test(attribute.semantic)) dependencies.push(key("accessor", attribute.accessorId));
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
add("skin", value.id, value, dependencies);
|
|
2512
|
+
}
|
|
2513
|
+
for (const value of document.animations) add("animation", value.id, value, value.samplers.flatMap((sampler) => [key("accessor", sampler.inputAccessorId), key("accessor", sampler.outputAccessorId)]));
|
|
2514
|
+
add("provenance", "source", document.provenance, []);
|
|
2515
|
+
}
|
|
2516
|
+
function validateDag(nodes) {
|
|
2517
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
2518
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
2519
|
+
const queue = [];
|
|
2520
|
+
for (const node of nodes.values()) {
|
|
2521
|
+
remaining.set(node.key, node.dependencies.length);
|
|
2522
|
+
if (!node.dependencies.length) queue.push(node.key);
|
|
2523
|
+
for (const dependency of node.dependencies) {
|
|
2524
|
+
if (!nodes.has(dependency)) fail3("missing-reference");
|
|
2525
|
+
const users = dependents.get(dependency) ?? [];
|
|
2526
|
+
users.push(node.key);
|
|
2527
|
+
dependents.set(dependency, users);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
for (let index = 0; index < queue.length; index++) {
|
|
2531
|
+
for (const dependent of dependents.get(queue[index]) ?? []) {
|
|
2532
|
+
const count = remaining.get(dependent) - 1;
|
|
2533
|
+
remaining.set(dependent, count);
|
|
2534
|
+
if (!count) queue.push(dependent);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (queue.length !== nodes.size) fail3("graph-cycle");
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2085
2540
|
// src/index.ts
|
|
2086
2541
|
var packageName = "@plasius/gpu-model-core";
|
|
2087
2542
|
var packageBootstrap = Object.freeze({
|
|
@@ -2092,8 +2547,12 @@ var packageBootstrap = Object.freeze({
|
|
|
2092
2547
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2093
2548
|
0 && (module.exports = {
|
|
2094
2549
|
CANONICAL_GPU_MODEL_COORDINATE_SYSTEM,
|
|
2550
|
+
GPU_MODEL_DIAGNOSTIC_LIMITS,
|
|
2095
2551
|
GPU_MODEL_DOCUMENT_LIMITS,
|
|
2096
2552
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
2553
|
+
GPU_MODEL_REPAIR_RULES,
|
|
2554
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
2555
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
2097
2556
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
2098
2557
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
2099
2558
|
GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -2102,15 +2561,21 @@ var packageBootstrap = Object.freeze({
|
|
|
2102
2561
|
GPU_MODEL_STATIC_DEMO_MAX_EXTENT_METRES,
|
|
2103
2562
|
GPU_MODEL_STATIC_DEMO_NORMALIZATION_CONTRACT,
|
|
2104
2563
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
2564
|
+
GpuModelDiagnosticsError,
|
|
2105
2565
|
GpuModelDocumentError,
|
|
2566
|
+
GpuModelResourceGraphError,
|
|
2106
2567
|
assertGpuModelStaticDemoNormalization,
|
|
2107
2568
|
canCreateGpuModelDocument,
|
|
2108
2569
|
createAndVerifyGpuModelDocument,
|
|
2109
2570
|
createGpuModelDocument,
|
|
2571
|
+
createGpuModelResourceGraph,
|
|
2110
2572
|
createGpuModelStaticDemoCompilerInput,
|
|
2111
2573
|
encodeGpuModelDocumentCanonical,
|
|
2574
|
+
evaluateGpuModelDiagnostics,
|
|
2575
|
+
gpuModelResourceKey,
|
|
2112
2576
|
hashGpuModelDocumentCanonical,
|
|
2113
2577
|
isGpuModelDocument,
|
|
2578
|
+
isGpuModelResourceGraph,
|
|
2114
2579
|
packageBootstrap,
|
|
2115
2580
|
packageName,
|
|
2116
2581
|
verifyGpuModelResource
|