@plasius/gpu-model-core 0.2.0 → 0.4.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 +214 -0
- package/dist/index.cjs +786 -221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -1
- package/dist/index.d.ts +280 -1
- package/dist/index.js +776 -221
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,8 @@ __export(index_exports, {
|
|
|
25
25
|
GPU_MODEL_DOCUMENT_LIMITS: () => GPU_MODEL_DOCUMENT_LIMITS,
|
|
26
26
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION: () => GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
27
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,
|
|
28
30
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS: () => GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
29
31
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG: () => GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
30
32
|
GPU_MODEL_STATIC_DEMO_LIMITS: () => GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -35,15 +37,23 @@ __export(index_exports, {
|
|
|
35
37
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION: () => GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
36
38
|
GpuModelDiagnosticsError: () => GpuModelDiagnosticsError,
|
|
37
39
|
GpuModelDocumentError: () => GpuModelDocumentError,
|
|
40
|
+
GpuModelResourceGraphError: () => GpuModelResourceGraphError,
|
|
41
|
+
MODEL_REGISTRY_LIMITS: () => MODEL_REGISTRY_LIMITS,
|
|
42
|
+
ModelRegistryError: () => ModelRegistryError,
|
|
38
43
|
assertGpuModelStaticDemoNormalization: () => assertGpuModelStaticDemoNormalization,
|
|
39
44
|
canCreateGpuModelDocument: () => canCreateGpuModelDocument,
|
|
40
45
|
createAndVerifyGpuModelDocument: () => createAndVerifyGpuModelDocument,
|
|
41
46
|
createGpuModelDocument: () => createGpuModelDocument,
|
|
47
|
+
createGpuModelResourceGraph: () => createGpuModelResourceGraph,
|
|
42
48
|
createGpuModelStaticDemoCompilerInput: () => createGpuModelStaticDemoCompilerInput,
|
|
49
|
+
createModelConversionRegistry: () => createModelConversionRegistry,
|
|
50
|
+
createModelResultBase: () => createModelResultBase,
|
|
43
51
|
encodeGpuModelDocumentCanonical: () => encodeGpuModelDocumentCanonical,
|
|
44
52
|
evaluateGpuModelDiagnostics: () => evaluateGpuModelDiagnostics,
|
|
53
|
+
gpuModelResourceKey: () => gpuModelResourceKey,
|
|
45
54
|
hashGpuModelDocumentCanonical: () => hashGpuModelDocumentCanonical,
|
|
46
55
|
isGpuModelDocument: () => isGpuModelDocument,
|
|
56
|
+
isGpuModelResourceGraph: () => isGpuModelResourceGraph,
|
|
47
57
|
packageBootstrap: () => packageBootstrap,
|
|
48
58
|
packageName: () => packageName,
|
|
49
59
|
verifyGpuModelResource: () => verifyGpuModelResource
|
|
@@ -224,9 +234,9 @@ function readRecord(value, path, allowedKeys) {
|
|
|
224
234
|
}
|
|
225
235
|
return value;
|
|
226
236
|
}
|
|
227
|
-
function requireKey(
|
|
228
|
-
if (!Object.hasOwn(
|
|
229
|
-
return
|
|
237
|
+
function requireKey(record4, key, path) {
|
|
238
|
+
if (!Object.hasOwn(record4, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
|
|
239
|
+
return record4[key];
|
|
230
240
|
}
|
|
231
241
|
function readArray(value, path, maximum, minimum = 0) {
|
|
232
242
|
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
@@ -305,9 +315,9 @@ function readNumberTuple(value, path, length, minimum, maximum) {
|
|
|
305
315
|
const input = readArray(value, path, length, length);
|
|
306
316
|
return Object.freeze(input.map((entry, index) => readFinite(entry, `${path}[${index}]`, minimum, maximum)));
|
|
307
317
|
}
|
|
308
|
-
function readOptional(
|
|
309
|
-
if (!Object.hasOwn(
|
|
310
|
-
return parser(
|
|
318
|
+
function readOptional(record4, key, parser, path) {
|
|
319
|
+
if (!Object.hasOwn(record4, key)) return void 0;
|
|
320
|
+
return parser(record4[key], `${path}.${key}`);
|
|
311
321
|
}
|
|
312
322
|
function assignOptional(target, key, value) {
|
|
313
323
|
if (value !== void 0) Object.assign(target, { [key]: value });
|
|
@@ -329,17 +339,17 @@ function readMetadataValue(value, path, depth, budget) {
|
|
|
329
339
|
return Object.freeze(input.map((entry, index) => readMetadataValue(entry, `${path}[${index}]`, depth + 1, budget)));
|
|
330
340
|
}
|
|
331
341
|
if (typeof value !== "object" || value === null) return fail("invalid-type", path, "metadata value is not JSON-compatible");
|
|
332
|
-
const
|
|
333
|
-
const prototype = Object.getPrototypeOf(
|
|
342
|
+
const record4 = value;
|
|
343
|
+
const prototype = Object.getPrototypeOf(record4);
|
|
334
344
|
if (prototype !== Object.prototype && prototype !== null) return fail("invalid-type", path, "metadata objects must be plain objects");
|
|
335
|
-
const keys = Reflect.ownKeys(
|
|
345
|
+
const keys = Reflect.ownKeys(record4);
|
|
336
346
|
if (keys.length > GPU_MODEL_DOCUMENT_LIMITS.metadataObjectKeys) return fail("limit-exceeded", path, "metadata object has too many keys");
|
|
337
347
|
const output = {};
|
|
338
348
|
for (const key of keys) {
|
|
339
349
|
if (typeof key !== "string") return fail("unknown-key", path, "metadata symbol keys are not permitted");
|
|
340
350
|
if (PROHIBITED_KEYS.has(key)) return fail("unknown-key", `${path}.${key}`, "prohibited metadata key");
|
|
341
351
|
readString(key, `${path}.[key]`, GPU_MODEL_DOCUMENT_LIMITS.metadataKeyLength, METADATA_KEY);
|
|
342
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
352
|
+
const descriptor = Object.getOwnPropertyDescriptor(record4, key);
|
|
343
353
|
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) return fail("invalid-type", `${path}.${key}`, "metadata properties must be enumerable data properties");
|
|
344
354
|
output[key] = readMetadataValue(descriptor.value, `${path}.${key}`, depth + 1, budget);
|
|
345
355
|
}
|
|
@@ -352,9 +362,9 @@ function readMetadata(value, path, budget) {
|
|
|
352
362
|
}
|
|
353
363
|
function parseCoordinateSystem(value) {
|
|
354
364
|
const path = "$.coordinateSystem";
|
|
355
|
-
const
|
|
365
|
+
const record4 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
|
|
356
366
|
for (const [key, expected] of Object.entries(CANONICAL_GPU_MODEL_COORDINATE_SYSTEM)) {
|
|
357
|
-
if (requireKey(
|
|
367
|
+
if (requireKey(record4, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
|
|
358
368
|
}
|
|
359
369
|
return CANONICAL_GPU_MODEL_COORDINATE_SYSTEM;
|
|
360
370
|
}
|
|
@@ -366,19 +376,19 @@ function parseMatrix(value, path, requireAffine = true) {
|
|
|
366
376
|
return matrix;
|
|
367
377
|
}
|
|
368
378
|
function parseBounds(value, path) {
|
|
369
|
-
const
|
|
370
|
-
const min = readNumberTuple(requireKey(
|
|
371
|
-
const max = readNumberTuple(requireKey(
|
|
379
|
+
const record4 = readRecord(value, path, ["min", "max"]);
|
|
380
|
+
const min = readNumberTuple(requireKey(record4, "min", path), `${path}.min`, 3);
|
|
381
|
+
const max = readNumberTuple(requireKey(record4, "max", path), `${path}.max`, 3);
|
|
372
382
|
for (let index = 0; index < 3; index += 1) {
|
|
373
383
|
if (min[index] > max[index]) return fail("invalid-value", path, "bounds min cannot exceed max");
|
|
374
384
|
}
|
|
375
385
|
return { min, max };
|
|
376
386
|
}
|
|
377
387
|
function parseTransformTrs(value, path) {
|
|
378
|
-
const
|
|
379
|
-
const translation = readNumberTuple(requireKey(
|
|
380
|
-
const rotation = readNumberTuple(requireKey(
|
|
381
|
-
const scale = readNumberTuple(requireKey(
|
|
388
|
+
const record4 = readRecord(value, path, ["translation", "rotation", "scale"]);
|
|
389
|
+
const translation = readNumberTuple(requireKey(record4, "translation", path), `${path}.translation`, 3);
|
|
390
|
+
const rotation = readNumberTuple(requireKey(record4, "rotation", path), `${path}.rotation`, 4);
|
|
391
|
+
const scale = readNumberTuple(requireKey(record4, "scale", path), `${path}.scale`, 3);
|
|
382
392
|
const magnitude = Math.hypot(...rotation);
|
|
383
393
|
if (!almostEqual(magnitude, 1, 1e-4)) return fail("invalid-value", `${path}.rotation`, "rotation quaternion must be normalized");
|
|
384
394
|
return { translation, rotation, scale };
|
|
@@ -400,25 +410,25 @@ function intrinsicBlobArrayBuffer(blob) {
|
|
|
400
410
|
return Reflect.apply(blobArrayBuffer, blob, []);
|
|
401
411
|
}
|
|
402
412
|
function parseResourceHeader(value, path) {
|
|
403
|
-
const
|
|
404
|
-
const kind = readEnum(requireKey(
|
|
405
|
-
const mimeType = readString(requireKey(
|
|
413
|
+
const record4 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
|
|
414
|
+
const kind = readEnum(requireKey(record4, "kind", path), `${path}.kind`, ["buffer", "image"]);
|
|
415
|
+
const mimeType = readString(requireKey(record4, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
|
|
406
416
|
if (kind === "image" ? !IMAGE_MIME_TYPES.has(mimeType) : !BUFFER_MIME_TYPES.has(mimeType)) {
|
|
407
417
|
return fail("invalid-value", `${path}.mimeType`, `unsupported ${kind} MIME type`);
|
|
408
418
|
}
|
|
409
|
-
const payload = requireKey(
|
|
419
|
+
const payload = requireKey(record4, "payload", path);
|
|
410
420
|
if (!(payload instanceof Blob)) return fail("invalid-type", `${path}.payload`, "expected an immutable Blob payload");
|
|
411
421
|
if (Object.getPrototypeOf(payload) !== Blob.prototype || Reflect.ownKeys(payload).some((key) => typeof key === "string")) {
|
|
412
422
|
return fail("invalid-type", `${path}.payload`, "expected a native Blob without caller-defined properties");
|
|
413
423
|
}
|
|
414
|
-
const byteLength = readSafeInteger(requireKey(
|
|
424
|
+
const byteLength = readSafeInteger(requireKey(record4, "byteLength", path), `${path}.byteLength`, 1);
|
|
415
425
|
if (intrinsicBlobSize(payload) !== byteLength) return fail("payload-length-mismatch", `${path}.payload`, "payload byte length does not match byteLength");
|
|
416
426
|
const payloadType = intrinsicBlobType(payload);
|
|
417
427
|
if (payloadType && payloadType.toLowerCase() !== mimeType) return fail("invalid-value", `${path}.payload`, "payload MIME type does not match mimeType");
|
|
418
428
|
return {
|
|
419
|
-
id: readId(requireKey(
|
|
429
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
420
430
|
kind,
|
|
421
|
-
contentHash: readSha256(requireKey(
|
|
431
|
+
contentHash: readSha256(requireKey(record4, "contentHash", path), `${path}.contentHash`),
|
|
422
432
|
byteLength,
|
|
423
433
|
mimeType,
|
|
424
434
|
payload
|
|
@@ -427,11 +437,11 @@ function parseResourceHeader(value, path) {
|
|
|
427
437
|
function resolveVerificationLimits(options) {
|
|
428
438
|
const overrides = options?.limits;
|
|
429
439
|
if (overrides === void 0) return GPU_MODEL_RESOURCE_VERIFICATION_LIMITS;
|
|
430
|
-
const
|
|
440
|
+
const record4 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
|
|
431
441
|
const output = { ...GPU_MODEL_RESOURCE_VERIFICATION_LIMITS };
|
|
432
442
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS)) {
|
|
433
|
-
if (!Object.hasOwn(
|
|
434
|
-
const value = readSafeInteger(
|
|
443
|
+
if (!Object.hasOwn(record4, key)) continue;
|
|
444
|
+
const value = readSafeInteger(record4[key], `$.verification.limits.${key}`, 1, ceiling);
|
|
435
445
|
Object.assign(output, { [key]: value });
|
|
436
446
|
}
|
|
437
447
|
if (output.maxImageBytes > output.maxResourceBytes) output.maxImageBytes = output.maxResourceBytes;
|
|
@@ -543,13 +553,13 @@ function createVerificationDeadline(timeoutMs, callerSignal, path) {
|
|
|
543
553
|
};
|
|
544
554
|
}
|
|
545
555
|
function parseInspection(value, path) {
|
|
546
|
-
const
|
|
556
|
+
const record4 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
|
|
547
557
|
const output = {
|
|
548
|
-
valid: readBoolean(requireKey(
|
|
549
|
-
detectedMimeType: readString(requireKey(
|
|
558
|
+
valid: readBoolean(requireKey(record4, "valid", path), `${path}.valid`),
|
|
559
|
+
detectedMimeType: readString(requireKey(record4, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
|
|
550
560
|
};
|
|
551
|
-
assignOptional(output, "width", readOptional(
|
|
552
|
-
assignOptional(output, "height", readOptional(
|
|
561
|
+
assignOptional(output, "width", readOptional(record4, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
562
|
+
assignOptional(output, "height", readOptional(record4, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
553
563
|
return output;
|
|
554
564
|
}
|
|
555
565
|
function bytesStartWith(bytes, signature) {
|
|
@@ -664,62 +674,62 @@ async function verifyGpuModelResource(value, port, options) {
|
|
|
664
674
|
}
|
|
665
675
|
}
|
|
666
676
|
function parseNode(value, path, budget) {
|
|
667
|
-
const
|
|
668
|
-
const children = Object.freeze(readArray(requireKey(
|
|
677
|
+
const record4 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
|
|
678
|
+
const children = Object.freeze(readArray(requireKey(record4, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
|
|
669
679
|
consumeBudget(budget, "sceneEdges", children.length, GPU_MODEL_DOCUMENT_LIMITS.sceneEdges, path, "scene edge");
|
|
670
680
|
ensureUnique(children, `${path}.children`, "duplicate child node");
|
|
671
681
|
const output = {
|
|
672
|
-
id: readId(requireKey(
|
|
682
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
673
683
|
children,
|
|
674
|
-
localMatrix: parseMatrix(requireKey(
|
|
684
|
+
localMatrix: parseMatrix(requireKey(record4, "localMatrix", path), `${path}.localMatrix`)
|
|
675
685
|
};
|
|
676
|
-
assignOptional(output, "name", readOptional(
|
|
677
|
-
assignOptional(output, "meshId", readOptional(
|
|
678
|
-
assignOptional(output, "skinId", readOptional(
|
|
679
|
-
assignOptional(output, "metadata", readOptional(
|
|
686
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
687
|
+
assignOptional(output, "meshId", readOptional(record4, "meshId", readId, path));
|
|
688
|
+
assignOptional(output, "skinId", readOptional(record4, "skinId", readId, path));
|
|
689
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
680
690
|
return output;
|
|
681
691
|
}
|
|
682
692
|
function parseAccessor(value, path, budget) {
|
|
683
|
-
const
|
|
684
|
-
const componentType = readEnum(requireKey(
|
|
685
|
-
const elementType = readEnum(requireKey(
|
|
686
|
-
const normalized = readOptional(
|
|
693
|
+
const record4 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
|
|
694
|
+
const componentType = readEnum(requireKey(record4, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
|
|
695
|
+
const elementType = readEnum(requireKey(record4, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
|
|
696
|
+
const normalized = readOptional(record4, "normalized", readBoolean, path);
|
|
687
697
|
if (normalized === true && componentType === "f32") return fail("invalid-value", `${path}.normalized`, "floating-point accessors cannot be normalized");
|
|
688
698
|
if (elementType.startsWith("mat") && componentType !== "f32") return fail("invalid-value", `${path}.componentType`, "matrix accessors must use f32 components");
|
|
689
699
|
const expectedComponents = ELEMENT_COMPONENTS[elementType];
|
|
690
|
-
const min = readOptional(
|
|
691
|
-
const max = readOptional(
|
|
700
|
+
const min = readOptional(record4, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
701
|
+
const max = readOptional(record4, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
692
702
|
if (min === void 0 !== (max === void 0)) return fail("invalid-value", path, "accessor min and max must be supplied together");
|
|
693
703
|
if (min && max) {
|
|
694
|
-
const [componentMinimum, componentMaximum,
|
|
704
|
+
const [componentMinimum, componentMaximum, integer2] = COMPONENT_RANGES[componentType];
|
|
695
705
|
for (let index = 0; index < expectedComponents; index += 1) {
|
|
696
706
|
const minimum = min[index];
|
|
697
707
|
const maximum = max[index];
|
|
698
708
|
if (minimum > maximum) return fail("invalid-value", path, "accessor min cannot exceed max");
|
|
699
|
-
if (minimum < componentMinimum || maximum > componentMaximum ||
|
|
709
|
+
if (minimum < componentMinimum || maximum > componentMaximum || integer2 && (!Number.isInteger(minimum) || !Number.isInteger(maximum))) {
|
|
700
710
|
return fail("invalid-value", path, "accessor bounds are outside the component representation");
|
|
701
711
|
}
|
|
702
712
|
}
|
|
703
713
|
}
|
|
704
|
-
const count = readSafeInteger(requireKey(
|
|
714
|
+
const count = readSafeInteger(requireKey(record4, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
|
|
705
715
|
consumeBudget(budget, "accessorElements", count * expectedComponents, GPU_MODEL_DOCUMENT_LIMITS.accessorElements, path, "accessor element component");
|
|
706
716
|
const output = {
|
|
707
|
-
id: readId(requireKey(
|
|
708
|
-
resourceId: readId(requireKey(
|
|
709
|
-
byteOffset: readSafeInteger(requireKey(
|
|
717
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
718
|
+
resourceId: readId(requireKey(record4, "resourceId", path), `${path}.resourceId`),
|
|
719
|
+
byteOffset: readSafeInteger(requireKey(record4, "byteOffset", path), `${path}.byteOffset`),
|
|
710
720
|
count,
|
|
711
721
|
componentType,
|
|
712
722
|
elementType
|
|
713
723
|
};
|
|
714
|
-
assignOptional(output, "byteStride", readOptional(
|
|
724
|
+
assignOptional(output, "byteStride", readOptional(record4, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
|
|
715
725
|
assignOptional(output, "normalized", normalized);
|
|
716
726
|
assignOptional(output, "min", min);
|
|
717
727
|
assignOptional(output, "max", max);
|
|
718
728
|
return output;
|
|
719
729
|
}
|
|
720
730
|
function parsePrimitive(value, path, budget) {
|
|
721
|
-
const
|
|
722
|
-
const attributesInput = readArray(requireKey(
|
|
731
|
+
const record4 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
|
|
732
|
+
const attributesInput = readArray(requireKey(record4, "attributes", path), `${path}.attributes`, 64, 1);
|
|
723
733
|
consumeBudget(budget, "primitiveAttributes", attributesInput.length, GPU_MODEL_DOCUMENT_LIMITS.primitiveAttributes, path, "primitive attribute");
|
|
724
734
|
const attributes = Object.freeze(attributesInput.map((entry, index) => {
|
|
725
735
|
const entryPath = `${path}.attributes[${index}]`;
|
|
@@ -732,53 +742,53 @@ function parsePrimitive(value, path, budget) {
|
|
|
732
742
|
ensureUnique(attributes.map(({ semantic }) => semantic), `${path}.attributes`, "duplicate attribute semantic");
|
|
733
743
|
if (!attributes.some(({ semantic }) => semantic === "POSITION")) return fail("invalid-value", `${path}.attributes`, "primitive requires POSITION");
|
|
734
744
|
const output = {
|
|
735
|
-
id: readId(requireKey(
|
|
736
|
-
topology: readEnum(requireKey(
|
|
745
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
746
|
+
topology: readEnum(requireKey(record4, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
|
|
737
747
|
attributes
|
|
738
748
|
};
|
|
739
|
-
assignOptional(output, "indicesAccessorId", readOptional(
|
|
740
|
-
assignOptional(output, "materialId", readOptional(
|
|
741
|
-
assignOptional(output, "metadata", readOptional(
|
|
749
|
+
assignOptional(output, "indicesAccessorId", readOptional(record4, "indicesAccessorId", readId, path));
|
|
750
|
+
assignOptional(output, "materialId", readOptional(record4, "materialId", readId, path));
|
|
751
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
742
752
|
return output;
|
|
743
753
|
}
|
|
744
754
|
function parseMesh(value, path, budget) {
|
|
745
|
-
const
|
|
746
|
-
const input = readArray(requireKey(
|
|
755
|
+
const record4 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
|
|
756
|
+
const input = readArray(requireKey(record4, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
747
757
|
consumeBudget(budget, "primitives", input.length, GPU_MODEL_DOCUMENT_LIMITS.primitives, path, "primitive");
|
|
748
758
|
const output = {
|
|
749
|
-
id: readId(requireKey(
|
|
759
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
750
760
|
primitives: Object.freeze(input.map((entry, index) => parsePrimitive(entry, `${path}.primitives[${index}]`, budget)))
|
|
751
761
|
};
|
|
752
|
-
assignOptional(output, "name", readOptional(
|
|
753
|
-
assignOptional(output, "metadata", readOptional(
|
|
762
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
763
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
754
764
|
return output;
|
|
755
765
|
}
|
|
756
766
|
function parseTextureTransform(value, path) {
|
|
757
|
-
const
|
|
758
|
-
if (Reflect.ownKeys(
|
|
767
|
+
const record4 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
|
|
768
|
+
if (Reflect.ownKeys(record4).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
|
|
759
769
|
const output = {};
|
|
760
|
-
assignOptional(output, "offset", readOptional(
|
|
761
|
-
assignOptional(output, "rotationRadians", readOptional(
|
|
762
|
-
assignOptional(output, "scale", readOptional(
|
|
770
|
+
assignOptional(output, "offset", readOptional(record4, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
771
|
+
assignOptional(output, "rotationRadians", readOptional(record4, "rotationRadians", readFinite, path));
|
|
772
|
+
assignOptional(output, "scale", readOptional(record4, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
763
773
|
return output;
|
|
764
774
|
}
|
|
765
775
|
function parseTextureBinding(value, path) {
|
|
766
|
-
const
|
|
776
|
+
const record4 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
|
|
767
777
|
const output = {
|
|
768
|
-
textureId: readId(requireKey(
|
|
769
|
-
colorSpace: readEnum(requireKey(
|
|
770
|
-
intendedUsage: readEnum(requireKey(
|
|
778
|
+
textureId: readId(requireKey(record4, "textureId", path), `${path}.textureId`),
|
|
779
|
+
colorSpace: readEnum(requireKey(record4, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
780
|
+
intendedUsage: readEnum(requireKey(record4, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
|
|
771
781
|
};
|
|
772
|
-
assignOptional(output, "samplerId", readOptional(
|
|
773
|
-
assignOptional(output, "texCoordSet", readOptional(
|
|
774
|
-
assignOptional(output, "transform", readOptional(
|
|
782
|
+
assignOptional(output, "samplerId", readOptional(record4, "samplerId", readId, path));
|
|
783
|
+
assignOptional(output, "texCoordSet", readOptional(record4, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
|
|
784
|
+
assignOptional(output, "transform", readOptional(record4, "transform", parseTextureTransform, path));
|
|
775
785
|
return output;
|
|
776
786
|
}
|
|
777
787
|
function parseMaterialTextures(value, path) {
|
|
778
788
|
const slots = Object.keys(MATERIAL_TEXTURE_SLOTS);
|
|
779
|
-
const
|
|
789
|
+
const record4 = readRecord(value, path, slots);
|
|
780
790
|
const output = {};
|
|
781
|
-
for (const slot of slots) assignOptional(output, slot, readOptional(
|
|
791
|
+
for (const slot of slots) assignOptional(output, slot, readOptional(record4, slot, parseTextureBinding, path));
|
|
782
792
|
return output;
|
|
783
793
|
}
|
|
784
794
|
function parseMaterial(value, path, budget) {
|
|
@@ -804,138 +814,138 @@ function parseMaterial(value, path, budget) {
|
|
|
804
814
|
"extensions",
|
|
805
815
|
"sourceMetadata"
|
|
806
816
|
];
|
|
807
|
-
const
|
|
808
|
-
const alphaMode = readEnum(requireKey(
|
|
809
|
-
const alphaCutoff = readOptional(
|
|
817
|
+
const record4 = readRecord(value, path, keys);
|
|
818
|
+
const alphaMode = readEnum(requireKey(record4, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
|
|
819
|
+
const alphaCutoff = readOptional(record4, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
|
|
810
820
|
if (alphaMode === "mask" && alphaCutoff === void 0) return fail("invalid-value", `${path}.alphaCutoff`, "mask materials require alphaCutoff");
|
|
811
821
|
if (alphaMode !== "mask" && alphaCutoff !== void 0) return fail("invalid-value", `${path}.alphaCutoff`, "alphaCutoff is only valid for mask materials");
|
|
812
822
|
const output = {
|
|
813
|
-
id: readId(requireKey(
|
|
814
|
-
workflow: readEnum(requireKey(
|
|
823
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
824
|
+
workflow: readEnum(requireKey(record4, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
|
|
815
825
|
alphaMode,
|
|
816
|
-
baseColorFactor: readNumberTuple(requireKey(
|
|
817
|
-
textures: parseMaterialTextures(requireKey(
|
|
818
|
-
extensions: readMetadata(requireKey(
|
|
819
|
-
sourceMetadata: readMetadata(requireKey(
|
|
826
|
+
baseColorFactor: readNumberTuple(requireKey(record4, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
|
|
827
|
+
textures: parseMaterialTextures(requireKey(record4, "textures", path), `${path}.textures`),
|
|
828
|
+
extensions: readMetadata(requireKey(record4, "extensions", path), `${path}.extensions`, budget),
|
|
829
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
820
830
|
};
|
|
821
|
-
assignOptional(output, "name", readOptional(
|
|
831
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
822
832
|
assignOptional(output, "alphaCutoff", alphaCutoff);
|
|
823
|
-
assignOptional(output, "doubleSided", readOptional(
|
|
833
|
+
assignOptional(output, "doubleSided", readOptional(record4, "doubleSided", readBoolean, path));
|
|
824
834
|
for (const key of ["metallicFactor", "roughnessFactor", "transmissionFactor", "clearcoatFactor", "clearcoatRoughnessFactor", "sheenRoughnessFactor", "occlusionStrength"]) {
|
|
825
|
-
assignOptional(output, key, readOptional(
|
|
835
|
+
assignOptional(output, key, readOptional(record4, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
826
836
|
}
|
|
827
|
-
assignOptional(output, "normalScale", readOptional(
|
|
828
|
-
assignOptional(output, "emissiveFactor", readOptional(
|
|
829
|
-
assignOptional(output, "sheenColorFactor", readOptional(
|
|
837
|
+
assignOptional(output, "normalScale", readOptional(record4, "normalScale", readFinite, path));
|
|
838
|
+
assignOptional(output, "emissiveFactor", readOptional(record4, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
|
|
839
|
+
assignOptional(output, "sheenColorFactor", readOptional(record4, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
|
|
830
840
|
return output;
|
|
831
841
|
}
|
|
832
842
|
function parseTextureSampler(value, path) {
|
|
833
|
-
const
|
|
834
|
-
const output = { id: readId(requireKey(
|
|
835
|
-
assignOptional(output, "wrapS", readOptional(
|
|
836
|
-
assignOptional(output, "wrapT", readOptional(
|
|
837
|
-
assignOptional(output, "minFilter", readOptional(
|
|
838
|
-
assignOptional(output, "magFilter", readOptional(
|
|
839
|
-
assignOptional(output, "mipFilter", readOptional(
|
|
843
|
+
const record4 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
|
|
844
|
+
const output = { id: readId(requireKey(record4, "id", path), `${path}.id`) };
|
|
845
|
+
assignOptional(output, "wrapS", readOptional(record4, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
846
|
+
assignOptional(output, "wrapT", readOptional(record4, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
847
|
+
assignOptional(output, "minFilter", readOptional(record4, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
848
|
+
assignOptional(output, "magFilter", readOptional(record4, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
849
|
+
assignOptional(output, "mipFilter", readOptional(record4, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
|
|
840
850
|
return output;
|
|
841
851
|
}
|
|
842
852
|
function parseTexture(value, path, budget) {
|
|
843
|
-
const
|
|
844
|
-
const usages = Object.freeze(readArray(requireKey(
|
|
853
|
+
const record4 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
|
|
854
|
+
const usages = Object.freeze(readArray(requireKey(record4, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
|
|
845
855
|
ensureUnique(usages, `${path}.intendedUsage`, "duplicate intended texture usage");
|
|
846
856
|
const output = {
|
|
847
|
-
id: readId(requireKey(
|
|
848
|
-
contentHash: readSha256(requireKey(
|
|
849
|
-
imageResourceId: readId(requireKey(
|
|
857
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
858
|
+
contentHash: readSha256(requireKey(record4, "contentHash", path), `${path}.contentHash`),
|
|
859
|
+
imageResourceId: readId(requireKey(record4, "imageResourceId", path), `${path}.imageResourceId`),
|
|
850
860
|
intendedUsage: usages,
|
|
851
|
-
colorSpace: readEnum(requireKey(
|
|
852
|
-
exportEmbeddingPolicy: readEnum(requireKey(
|
|
853
|
-
sourceMetadata: readMetadata(requireKey(
|
|
861
|
+
colorSpace: readEnum(requireKey(record4, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
862
|
+
exportEmbeddingPolicy: readEnum(requireKey(record4, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
|
|
863
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
854
864
|
};
|
|
855
|
-
assignOptional(output, "fileName", readOptional(
|
|
856
|
-
assignOptional(output, "relativePath", readOptional(
|
|
857
|
-
assignOptional(output, "mimeType", readOptional(
|
|
858
|
-
assignOptional(output, "byteLength", readOptional(
|
|
859
|
-
assignOptional(output, "sampler", readOptional(
|
|
865
|
+
assignOptional(output, "fileName", readOptional(record4, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
|
|
866
|
+
assignOptional(output, "relativePath", readOptional(record4, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
|
|
867
|
+
assignOptional(output, "mimeType", readOptional(record4, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
|
|
868
|
+
assignOptional(output, "byteLength", readOptional(record4, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
869
|
+
assignOptional(output, "sampler", readOptional(record4, "sampler", parseTextureSampler, path));
|
|
860
870
|
return output;
|
|
861
871
|
}
|
|
862
872
|
function parseSkeleton(value, path, budget) {
|
|
863
|
-
const
|
|
864
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
865
|
-
const rootJointIds = Object.freeze(readArray(requireKey(
|
|
873
|
+
const record4 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
|
|
874
|
+
const jointIds = Object.freeze(readArray(requireKey(record4, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
875
|
+
const rootJointIds = Object.freeze(readArray(requireKey(record4, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
|
|
866
876
|
consumeBudget(budget, "skinJoints", jointIds.length + rootJointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "rig joint");
|
|
867
877
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skeleton joint");
|
|
868
878
|
ensureUnique(rootJointIds, `${path}.rootJointIds`, "duplicate root joint");
|
|
869
879
|
const output = {
|
|
870
|
-
id: readId(requireKey(
|
|
880
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
871
881
|
jointIds,
|
|
872
882
|
rootJointIds,
|
|
873
|
-
sourceMetadata: readMetadata(requireKey(
|
|
883
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
874
884
|
};
|
|
875
|
-
assignOptional(output, "name", readOptional(
|
|
885
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
876
886
|
return output;
|
|
877
887
|
}
|
|
878
888
|
function parseJoint(value, path, budget) {
|
|
879
|
-
const
|
|
889
|
+
const record4 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
|
|
880
890
|
const output = {
|
|
881
|
-
id: readId(requireKey(
|
|
882
|
-
nodeId: readId(requireKey(
|
|
883
|
-
restLocalTransform: parseTransformTrs(requireKey(
|
|
884
|
-
sourceMetadata: readMetadata(requireKey(
|
|
891
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
892
|
+
nodeId: readId(requireKey(record4, "nodeId", path), `${path}.nodeId`),
|
|
893
|
+
restLocalTransform: parseTransformTrs(requireKey(record4, "restLocalTransform", path), `${path}.restLocalTransform`),
|
|
894
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
885
895
|
};
|
|
886
|
-
assignOptional(output, "name", readOptional(
|
|
887
|
-
assignOptional(output, "parentJointId", readOptional(
|
|
888
|
-
assignOptional(output, "inverseBindMatrix", readOptional(
|
|
896
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
897
|
+
assignOptional(output, "parentJointId", readOptional(record4, "parentJointId", readId, path));
|
|
898
|
+
assignOptional(output, "inverseBindMatrix", readOptional(record4, "inverseBindMatrix", parseMatrix, path));
|
|
889
899
|
return output;
|
|
890
900
|
}
|
|
891
|
-
function parseWeightRows(value, path, rows, columns,
|
|
901
|
+
function parseWeightRows(value, path, rows, columns, integer2, budget) {
|
|
892
902
|
const input = readArray(value, path, rows, rows);
|
|
893
903
|
consumeBudget(budget, "weightVertices", input.length, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, path, "weight vertex");
|
|
894
904
|
consumeBudget(budget, "weightInfluences", input.length * columns, GPU_MODEL_DOCUMENT_LIMITS.weightInfluences, path, "weight influence");
|
|
895
905
|
return Object.freeze(input.map((row, rowIndex) => {
|
|
896
|
-
const parsed = readNumberTuple(row, `${path}[${rowIndex}]`, columns, 0,
|
|
897
|
-
if (
|
|
906
|
+
const parsed = readNumberTuple(row, `${path}[${rowIndex}]`, columns, 0, integer2 ? Number.MAX_SAFE_INTEGER : 1);
|
|
907
|
+
if (integer2 && parsed.some((entry) => !Number.isSafeInteger(entry))) return fail("invalid-value", `${path}[${rowIndex}]`, "joint indices must be safe integers");
|
|
898
908
|
return parsed;
|
|
899
909
|
}));
|
|
900
910
|
}
|
|
901
911
|
function parseVertexWeightSet(value, path, budget) {
|
|
902
|
-
const
|
|
903
|
-
const influences = readSafeInteger(requireKey(
|
|
912
|
+
const record4 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
|
|
913
|
+
const influences = readSafeInteger(requireKey(record4, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
|
|
904
914
|
if (influences % 4 !== 0) return fail("invalid-value", `${path}.influencesPerVertex`, "influencesPerVertex must be a multiple of four");
|
|
905
|
-
const jointRows = readArray(requireKey(
|
|
906
|
-
const weightRows = readArray(requireKey(
|
|
915
|
+
const jointRows = readArray(requireKey(record4, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
916
|
+
const weightRows = readArray(requireKey(record4, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
907
917
|
if (jointRows.length !== weightRows.length) return fail("invalid-value", path, "joint and weight row counts must match");
|
|
908
918
|
return {
|
|
909
|
-
primitiveId: readId(requireKey(
|
|
919
|
+
primitiveId: readId(requireKey(record4, "primitiveId", path), `${path}.primitiveId`),
|
|
910
920
|
influencesPerVertex: influences,
|
|
911
921
|
joints: parseWeightRows(jointRows, `${path}.joints`, jointRows.length, influences, true, budget),
|
|
912
922
|
weights: parseWeightRows(weightRows, `${path}.weights`, weightRows.length, influences, false, budget)
|
|
913
923
|
};
|
|
914
924
|
}
|
|
915
925
|
function parseSkin(value, path, budget) {
|
|
916
|
-
const
|
|
917
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
926
|
+
const record4 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
|
|
927
|
+
const jointIds = Object.freeze(readArray(requireKey(record4, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
918
928
|
consumeBudget(budget, "skinJoints", jointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "skin joint");
|
|
919
929
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skin joint");
|
|
920
|
-
const inverseInput = readArray(requireKey(
|
|
930
|
+
const inverseInput = readArray(requireKey(record4, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
|
|
921
931
|
if (inverseInput.length !== jointIds.length) return fail("invalid-value", `${path}.inverseBindMatrices`, "one inverse bind matrix is required per skin joint");
|
|
922
|
-
const weightInput = readArray(requireKey(
|
|
932
|
+
const weightInput = readArray(requireKey(record4, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
923
933
|
const weightSets = Object.freeze(weightInput.map((entry, index) => parseVertexWeightSet(entry, `${path}.weightSets[${index}]`, budget)));
|
|
924
934
|
ensureUnique(weightSets.map(({ primitiveId }) => primitiveId), `${path}.weightSets`, "duplicate primitive weight set");
|
|
925
935
|
return {
|
|
926
|
-
id: readId(requireKey(
|
|
927
|
-
skeletonId: readId(requireKey(
|
|
936
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
937
|
+
skeletonId: readId(requireKey(record4, "skeletonId", path), `${path}.skeletonId`),
|
|
928
938
|
jointIds,
|
|
929
939
|
inverseBindMatrices: Object.freeze(inverseInput.map((entry, index) => parseMatrix(entry, `${path}.inverseBindMatrices[${index}]`))),
|
|
930
940
|
weightSets,
|
|
931
|
-
sourceMetadata: readMetadata(requireKey(
|
|
941
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
932
942
|
};
|
|
933
943
|
}
|
|
934
944
|
function parseBlendShapeDelta(value, path) {
|
|
935
|
-
const
|
|
936
|
-
const output = { primitiveId: readId(requireKey(
|
|
945
|
+
const record4 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
|
|
946
|
+
const output = { primitiveId: readId(requireKey(record4, "primitiveId", path), `${path}.primitiveId`) };
|
|
937
947
|
for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
|
|
938
|
-
assignOptional(output, key, readOptional(
|
|
948
|
+
assignOptional(output, key, readOptional(record4, key, readId, path));
|
|
939
949
|
}
|
|
940
950
|
if (!output.positionAccessorId && !output.normalAccessorId && !output.tangentAccessorId) {
|
|
941
951
|
return fail("invalid-value", path, "blend-shape delta requires at least one accessor");
|
|
@@ -943,8 +953,8 @@ function parseBlendShapeDelta(value, path) {
|
|
|
943
953
|
return output;
|
|
944
954
|
}
|
|
945
955
|
function parseBlendShape(value, path, budget) {
|
|
946
|
-
const
|
|
947
|
-
const deltaInput = readArray(requireKey(
|
|
956
|
+
const record4 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
|
|
957
|
+
const deltaInput = readArray(requireKey(record4, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
948
958
|
consumeBudget(
|
|
949
959
|
budget,
|
|
950
960
|
"blendShapeDeltas",
|
|
@@ -956,96 +966,96 @@ function parseBlendShape(value, path, budget) {
|
|
|
956
966
|
const deltas = Object.freeze(deltaInput.map((entry, index) => parseBlendShapeDelta(entry, `${path}.deltas[${index}]`)));
|
|
957
967
|
ensureUnique(deltas.map(({ primitiveId }) => primitiveId), `${path}.deltas`, "duplicate blend-shape primitive delta");
|
|
958
968
|
const output = {
|
|
959
|
-
id: readId(requireKey(
|
|
960
|
-
meshId: readId(requireKey(
|
|
969
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
970
|
+
meshId: readId(requireKey(record4, "meshId", path), `${path}.meshId`),
|
|
961
971
|
deltas,
|
|
962
|
-
sourceMetadata: readMetadata(requireKey(
|
|
972
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
963
973
|
};
|
|
964
|
-
assignOptional(output, "name", readOptional(
|
|
965
|
-
assignOptional(output, "defaultWeight", readOptional(
|
|
974
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
975
|
+
assignOptional(output, "defaultWeight", readOptional(record4, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
966
976
|
return output;
|
|
967
977
|
}
|
|
968
978
|
function parseAnimationSampler(value, path, budget) {
|
|
969
|
-
const
|
|
970
|
-
const timesInput = readArray(requireKey(
|
|
971
|
-
const valuesInput = readArray(requireKey(
|
|
979
|
+
const record4 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
|
|
980
|
+
const timesInput = readArray(requireKey(record4, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
981
|
+
const valuesInput = readArray(requireKey(record4, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
972
982
|
consumeBudget(budget, "animationValues", timesInput.length + valuesInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationValues, path, "animation value");
|
|
973
983
|
const times = Object.freeze(timesInput.map((entry, index) => readFinite(entry, `${path}.inputTimesSeconds[${index}]`, 0)));
|
|
974
984
|
for (let index = 1; index < times.length; index += 1) {
|
|
975
985
|
if (times[index] <= times[index - 1]) return fail("invalid-value", `${path}.inputTimesSeconds`, "animation input times must be strictly increasing");
|
|
976
986
|
}
|
|
977
987
|
return {
|
|
978
|
-
id: readId(requireKey(
|
|
979
|
-
inputAccessorId: readId(requireKey(
|
|
980
|
-
outputAccessorId: readId(requireKey(
|
|
988
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
989
|
+
inputAccessorId: readId(requireKey(record4, "inputAccessorId", path), `${path}.inputAccessorId`),
|
|
990
|
+
outputAccessorId: readId(requireKey(record4, "outputAccessorId", path), `${path}.outputAccessorId`),
|
|
981
991
|
inputTimesSeconds: times,
|
|
982
992
|
outputValues: Object.freeze(valuesInput.map((entry, index) => readFinite(entry, `${path}.outputValues[${index}]`))),
|
|
983
|
-
interpolation: readEnum(requireKey(
|
|
984
|
-
sourceMetadata: readMetadata(requireKey(
|
|
993
|
+
interpolation: readEnum(requireKey(record4, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
|
|
994
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
985
995
|
};
|
|
986
996
|
}
|
|
987
997
|
function parseAnimationChannel(value, path, budget) {
|
|
988
|
-
const
|
|
998
|
+
const record4 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
|
|
989
999
|
return {
|
|
990
|
-
id: readId(requireKey(
|
|
991
|
-
samplerId: readId(requireKey(
|
|
992
|
-
targetType: readEnum(requireKey(
|
|
993
|
-
targetId: readId(requireKey(
|
|
994
|
-
sourceMetadata: readMetadata(requireKey(
|
|
1000
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
1001
|
+
samplerId: readId(requireKey(record4, "samplerId", path), `${path}.samplerId`),
|
|
1002
|
+
targetType: readEnum(requireKey(record4, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
|
|
1003
|
+
targetId: readId(requireKey(record4, "targetId", path), `${path}.targetId`),
|
|
1004
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
995
1005
|
};
|
|
996
1006
|
}
|
|
997
1007
|
function parseAnimation(value, path, budget) {
|
|
998
|
-
const
|
|
999
|
-
const samplerInput = readArray(requireKey(
|
|
1000
|
-
const channelInput = readArray(requireKey(
|
|
1008
|
+
const record4 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
|
|
1009
|
+
const samplerInput = readArray(requireKey(record4, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
|
|
1010
|
+
const channelInput = readArray(requireKey(record4, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
|
|
1001
1011
|
consumeBudget(budget, "animationSamplers", samplerInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, path, "animation sampler");
|
|
1002
1012
|
consumeBudget(budget, "animationChannels", channelInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, path, "animation channel");
|
|
1003
1013
|
const output = {
|
|
1004
|
-
id: readId(requireKey(
|
|
1005
|
-
durationSeconds: readFinite(requireKey(
|
|
1014
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
1015
|
+
durationSeconds: readFinite(requireKey(record4, "durationSeconds", path), `${path}.durationSeconds`, 0),
|
|
1006
1016
|
samplers: Object.freeze(samplerInput.map((entry, index) => parseAnimationSampler(entry, `${path}.samplers[${index}]`, budget))),
|
|
1007
1017
|
channels: Object.freeze(channelInput.map((entry, index) => parseAnimationChannel(entry, `${path}.channels[${index}]`, budget))),
|
|
1008
|
-
sourceMetadata: readMetadata(requireKey(
|
|
1018
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
1009
1019
|
};
|
|
1010
|
-
assignOptional(output, "name", readOptional(
|
|
1020
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
1011
1021
|
return output;
|
|
1012
1022
|
}
|
|
1013
1023
|
function parseAnalyticGeometry(value, path, budget) {
|
|
1014
|
-
const
|
|
1015
|
-
if (requireKey(
|
|
1024
|
+
const record4 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
|
|
1025
|
+
if (requireKey(record4, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
|
|
1016
1026
|
const output = {
|
|
1017
|
-
id: readId(requireKey(
|
|
1027
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
1018
1028
|
kind: "brep-placeholder",
|
|
1019
|
-
sourceResourceId: readId(requireKey(
|
|
1020
|
-
sourceUnit: readEnum(requireKey(
|
|
1029
|
+
sourceResourceId: readId(requireKey(record4, "sourceResourceId", path), `${path}.sourceResourceId`),
|
|
1030
|
+
sourceUnit: readEnum(requireKey(record4, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
|
|
1021
1031
|
};
|
|
1022
|
-
assignOptional(output, "tessellatedMeshId", readOptional(
|
|
1023
|
-
assignOptional(output, "metadata", readOptional(
|
|
1032
|
+
assignOptional(output, "tessellatedMeshId", readOptional(record4, "tessellatedMeshId", readId, path));
|
|
1033
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1024
1034
|
return output;
|
|
1025
1035
|
}
|
|
1026
1036
|
function parseProvenance(value, budget) {
|
|
1027
1037
|
const path = "$.provenance";
|
|
1028
|
-
const
|
|
1038
|
+
const record4 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
|
|
1029
1039
|
const output = {
|
|
1030
|
-
sourceFormat: readString(requireKey(
|
|
1031
|
-
sourceContentHash: readSha256(requireKey(
|
|
1032
|
-
converterId: readString(requireKey(
|
|
1033
|
-
converterVersion: readString(requireKey(
|
|
1040
|
+
sourceFormat: readString(requireKey(record4, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
|
|
1041
|
+
sourceContentHash: readSha256(requireKey(record4, "sourceContentHash", path), `${path}.sourceContentHash`),
|
|
1042
|
+
converterId: readString(requireKey(record4, "converterId", path), `${path}.converterId`, 128, TOKEN),
|
|
1043
|
+
converterVersion: readString(requireKey(record4, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
|
|
1034
1044
|
};
|
|
1035
|
-
assignOptional(output, "provider", readOptional(
|
|
1036
|
-
assignOptional(output, "sourceIdentifier", readOptional(
|
|
1037
|
-
assignOptional(output, "metadata", readOptional(
|
|
1045
|
+
assignOptional(output, "provider", readOptional(record4, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
1046
|
+
assignOptional(output, "sourceIdentifier", readOptional(record4, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
|
|
1047
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1038
1048
|
return output;
|
|
1039
1049
|
}
|
|
1040
1050
|
function parseDiagnostic(value, path, budget) {
|
|
1041
|
-
const
|
|
1051
|
+
const record4 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
|
|
1042
1052
|
const output = {
|
|
1043
|
-
severity: readEnum(requireKey(
|
|
1044
|
-
code: readString(requireKey(
|
|
1045
|
-
message: readString(requireKey(
|
|
1053
|
+
severity: readEnum(requireKey(record4, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
|
|
1054
|
+
code: readString(requireKey(record4, "code", path), `${path}.code`, 128, TOKEN),
|
|
1055
|
+
message: readString(requireKey(record4, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
|
|
1046
1056
|
};
|
|
1047
|
-
assignOptional(output, "path", readOptional(
|
|
1048
|
-
assignOptional(output, "metadata", readOptional(
|
|
1057
|
+
assignOptional(output, "path", readOptional(record4, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
|
|
1058
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1049
1059
|
return output;
|
|
1050
1060
|
}
|
|
1051
1061
|
function ensureUnique(values, path, detail = "duplicate identity") {
|
|
@@ -1686,11 +1696,11 @@ var DOCUMENT_KEYS = Object.freeze([
|
|
|
1686
1696
|
]);
|
|
1687
1697
|
function parseGpuModelDocument(value) {
|
|
1688
1698
|
const path = "$";
|
|
1689
|
-
const
|
|
1690
|
-
const schemaVersion = requireKey(
|
|
1699
|
+
const record4 = readRecord(value, path, DOCUMENT_KEYS);
|
|
1700
|
+
const schemaVersion = requireKey(record4, "schemaVersion", path);
|
|
1691
1701
|
if (schemaVersion !== GPU_MODEL_DOCUMENT_SCHEMA_VERSION) return fail("invalid-value", "$.schemaVersion", "unsupported schema version");
|
|
1692
1702
|
const parseCollection = (key, maximum, parser, minimum = 0) => {
|
|
1693
|
-
const input = readArray(requireKey(
|
|
1703
|
+
const input = readArray(requireKey(record4, key, path), `$.${key}`, maximum, minimum);
|
|
1694
1704
|
return Object.freeze(input.map((entry, index) => parser(entry, `$.${key}[${index}]`)));
|
|
1695
1705
|
};
|
|
1696
1706
|
const budget = {
|
|
@@ -1711,10 +1721,10 @@ function parseGpuModelDocument(value) {
|
|
|
1711
1721
|
geometryWorldAccessorInstances: 0,
|
|
1712
1722
|
geometryWorldVertices: 0
|
|
1713
1723
|
};
|
|
1714
|
-
const roots = Object.freeze(readArray(requireKey(
|
|
1724
|
+
const roots = Object.freeze(readArray(requireKey(record4, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
|
|
1715
1725
|
const document = {
|
|
1716
1726
|
schemaVersion,
|
|
1717
|
-
coordinateSystem: parseCoordinateSystem(requireKey(
|
|
1727
|
+
coordinateSystem: parseCoordinateSystem(requireKey(record4, "coordinateSystem", path)),
|
|
1718
1728
|
roots,
|
|
1719
1729
|
nodes: parseCollection("nodes", GPU_MODEL_DOCUMENT_LIMITS.nodes, (entry, entryPath) => parseNode(entry, entryPath, budget), 1),
|
|
1720
1730
|
resources: parseCollection("resources", GPU_MODEL_DOCUMENT_LIMITS.resources, parseVerifiedResource, 1),
|
|
@@ -1728,10 +1738,10 @@ function parseGpuModelDocument(value) {
|
|
|
1728
1738
|
blendShapes: parseCollection("blendShapes", GPU_MODEL_DOCUMENT_LIMITS.blendShapes, (entry, entryPath) => parseBlendShape(entry, entryPath, budget)),
|
|
1729
1739
|
animations: parseCollection("animations", GPU_MODEL_DOCUMENT_LIMITS.animations, (entry, entryPath) => parseAnimation(entry, entryPath, budget)),
|
|
1730
1740
|
analyticGeometry: parseCollection("analyticGeometry", GPU_MODEL_DOCUMENT_LIMITS.analyticGeometry, (entry, entryPath) => parseAnalyticGeometry(entry, entryPath, budget)),
|
|
1731
|
-
bounds: parseBounds(requireKey(
|
|
1732
|
-
provenance: parseProvenance(requireKey(
|
|
1741
|
+
bounds: parseBounds(requireKey(record4, "bounds", path), "$.bounds"),
|
|
1742
|
+
provenance: parseProvenance(requireKey(record4, "provenance", path), budget),
|
|
1733
1743
|
diagnostics: parseCollection("diagnostics", GPU_MODEL_DOCUMENT_LIMITS.diagnostics, (entry, entryPath) => parseDiagnostic(entry, entryPath, budget)),
|
|
1734
|
-
metadata: readMetadata(requireKey(
|
|
1744
|
+
metadata: readMetadata(requireKey(record4, "metadata", path), "$.metadata", budget)
|
|
1735
1745
|
};
|
|
1736
1746
|
if (document.meshes.length === 0) return fail("invalid-value", "$.meshes", "v1 document bounds require at least one canonical mesh");
|
|
1737
1747
|
validateReferences(document, budget);
|
|
@@ -1768,8 +1778,8 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1768
1778
|
value.resources.forEach((resource, index) => assertVerifiedResourceEvidence(resource, limits, `$.resources[${index}]`));
|
|
1769
1779
|
return value;
|
|
1770
1780
|
}
|
|
1771
|
-
const
|
|
1772
|
-
const resourceInput = readArray(requireKey(
|
|
1781
|
+
const record4 = readRecord(value, "$", DOCUMENT_KEYS);
|
|
1782
|
+
const resourceInput = readArray(requireKey(record4, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
|
|
1773
1783
|
const parsed = resourceInput.map((entry, index) => {
|
|
1774
1784
|
if (typeof entry === "object" && entry !== null && verifiedResources.has(entry) && verifiedResourceBytes.has(entry)) return entry;
|
|
1775
1785
|
return parseResourceHeader(entry, `$.resources[${index}]`);
|
|
@@ -1782,7 +1792,7 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1782
1792
|
}
|
|
1783
1793
|
const candidate = {};
|
|
1784
1794
|
for (const key of DOCUMENT_KEYS) {
|
|
1785
|
-
if (Object.hasOwn(
|
|
1795
|
+
if (Object.hasOwn(record4, key)) candidate[key] = record4[key];
|
|
1786
1796
|
}
|
|
1787
1797
|
candidate.resources = verified;
|
|
1788
1798
|
return createGpuModelDocument(candidate);
|
|
@@ -1847,11 +1857,11 @@ async function hashGpuModelDocumentCanonical(document) {
|
|
|
1847
1857
|
return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
1848
1858
|
}
|
|
1849
1859
|
function resolveStaticDemoLimits(options) {
|
|
1850
|
-
const
|
|
1851
|
-
const enabled = readBoolean(requireKey(
|
|
1860
|
+
const record4 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
|
|
1861
|
+
const enabled = readBoolean(requireKey(record4, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
|
|
1852
1862
|
if (!enabled) return fail("feature-disabled", "$.options.pvoxModelsEnabled", `${GPU_MODEL_STATIC_DEMO_FEATURE_FLAG} is disabled`);
|
|
1853
|
-
if (!Object.hasOwn(
|
|
1854
|
-
const overrides = readRecord(
|
|
1863
|
+
if (!Object.hasOwn(record4, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
|
|
1864
|
+
const overrides = readRecord(record4.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
|
|
1855
1865
|
const limits = { ...GPU_MODEL_STATIC_DEMO_LIMITS };
|
|
1856
1866
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_STATIC_DEMO_LIMITS)) {
|
|
1857
1867
|
if (Object.hasOwn(overrides, key)) limits[key] = readSafeInteger(overrides[key], `$.options.limits.${key}`, 1, ceiling);
|
|
@@ -2220,15 +2230,15 @@ function snapshot(value, depth, budget, ancestors, memberLimit = GPU_MODEL_DIAGN
|
|
|
2220
2230
|
return value;
|
|
2221
2231
|
}
|
|
2222
2232
|
if (typeof value !== "object" || ancestors.has(value)) fail2();
|
|
2223
|
-
const
|
|
2233
|
+
const array2 = Array.isArray(value);
|
|
2224
2234
|
const prototype = Object.getPrototypeOf(value);
|
|
2225
|
-
if (
|
|
2235
|
+
if (array2 ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
|
|
2226
2236
|
const keys = Reflect.ownKeys(value);
|
|
2227
2237
|
const maxMembers = memberLimit;
|
|
2228
|
-
if (keys.length > maxMembers + (
|
|
2238
|
+
if (keys.length > maxMembers + (array2 ? 1 : 0)) fail2();
|
|
2229
2239
|
ancestors.add(value);
|
|
2230
2240
|
charge(budget, 2);
|
|
2231
|
-
if (
|
|
2241
|
+
if (array2) {
|
|
2232
2242
|
const length = Object.getOwnPropertyDescriptor(value, "length").value;
|
|
2233
2243
|
if (length > maxMembers || keys.length !== length + 1) fail2();
|
|
2234
2244
|
const result2 = [];
|
|
@@ -2313,7 +2323,7 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2313
2323
|
const diagnostics = [];
|
|
2314
2324
|
const repairs = [];
|
|
2315
2325
|
const losses = [];
|
|
2316
|
-
let
|
|
2326
|
+
let accepted2 = true;
|
|
2317
2327
|
for (const issue of issues) {
|
|
2318
2328
|
const base = { code: issue.code, path: issue.path, message: issue.message };
|
|
2319
2329
|
if (issue.kind === "violation") {
|
|
@@ -2324,20 +2334,565 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2324
2334
|
repairs.push(Object.freeze({ ruleId: rule.id, originalValue: issue.originalValue, action: rule.action, severity: rule.severity, affectedPath: issue.path, confidence: rule.confidence }));
|
|
2325
2335
|
diagnostics.push(Object.freeze({ ...base, kind: "repair", severity: rule.severity, ruleId: rule.id }));
|
|
2326
2336
|
} else {
|
|
2327
|
-
|
|
2337
|
+
accepted2 = false;
|
|
2328
2338
|
diagnostics.push(Object.freeze({ ...base, kind: "blocking-error", severity: "error", ruleId: issue.ruleId }));
|
|
2329
2339
|
}
|
|
2330
2340
|
} else if (issue.kind === "unsupported") {
|
|
2331
|
-
|
|
2341
|
+
accepted2 = false;
|
|
2332
2342
|
diagnostics.push(Object.freeze({ ...issue, severity: "error" }));
|
|
2333
2343
|
} else if (issue.kind === "loss") {
|
|
2334
|
-
if (!allowLoss)
|
|
2344
|
+
if (!allowLoss) accepted2 = false;
|
|
2335
2345
|
const loss = Object.freeze({ ...issue, severity: "warning" });
|
|
2336
2346
|
losses.push(loss);
|
|
2337
2347
|
diagnostics.push(loss);
|
|
2338
2348
|
} else diagnostics.push(Object.freeze({ ...issue, severity: "warning" }));
|
|
2339
2349
|
}
|
|
2340
|
-
return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2350
|
+
return Object.freeze({ mode, standard, accepted: accepted2, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// src/resource-graph.ts
|
|
2354
|
+
var GPU_MODEL_RESOURCE_GRAPH_LIMITS = Object.freeze({
|
|
2355
|
+
nodes: 65536,
|
|
2356
|
+
edges: 262144,
|
|
2357
|
+
packages: 256,
|
|
2358
|
+
pathLength: 1024
|
|
2359
|
+
});
|
|
2360
|
+
var GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION = "plasius.gpu-model-resource-graph/1";
|
|
2361
|
+
var KINDS = ["resource", "accessor", "texture", "material", "skeleton", "skin", "animation", "provenance", "package"];
|
|
2362
|
+
var GpuModelResourceGraphError = class extends TypeError {
|
|
2363
|
+
constructor(code) {
|
|
2364
|
+
super(`Invalid GPU model resource graph: ${code}`);
|
|
2365
|
+
this.code = code;
|
|
2366
|
+
this.name = "GpuModelResourceGraphError";
|
|
2367
|
+
}
|
|
2368
|
+
code;
|
|
2369
|
+
};
|
|
2370
|
+
function fail3(code = "invalid-value") {
|
|
2371
|
+
throw new GpuModelResourceGraphError(code);
|
|
2372
|
+
}
|
|
2373
|
+
function identifier(value) {
|
|
2374
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail3();
|
|
2375
|
+
return value;
|
|
2376
|
+
}
|
|
2377
|
+
function choice(value, choices) {
|
|
2378
|
+
if (typeof value !== "string" || !choices.includes(value)) fail3();
|
|
2379
|
+
return value;
|
|
2380
|
+
}
|
|
2381
|
+
function gpuModelResourceKey(kind, id) {
|
|
2382
|
+
return JSON.stringify([choice(kind, KINDS), identifier(id)]);
|
|
2383
|
+
}
|
|
2384
|
+
function record2(value, keys) {
|
|
2385
|
+
if (typeof value !== "object" || value === null || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
2386
|
+
const names = Reflect.ownKeys(value);
|
|
2387
|
+
if (names.length !== keys.length || names.some((key) => typeof key !== "string" || !keys.includes(key))) fail3();
|
|
2388
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2389
|
+
for (const key of keys) {
|
|
2390
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2391
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2392
|
+
result[key] = descriptor.value;
|
|
2393
|
+
}
|
|
2394
|
+
return result;
|
|
2395
|
+
}
|
|
2396
|
+
function array(value, maximum) {
|
|
2397
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail3();
|
|
2398
|
+
if (value.length > maximum) fail3("limit-exceeded");
|
|
2399
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
2400
|
+
const output = [];
|
|
2401
|
+
for (let index = 0; index < value.length; index++) {
|
|
2402
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
2403
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2404
|
+
output.push(descriptor.value);
|
|
2405
|
+
}
|
|
2406
|
+
return output;
|
|
2407
|
+
}
|
|
2408
|
+
function relativePath(value) {
|
|
2409
|
+
if (typeof value !== "string" || value.length > GPU_MODEL_RESOURCE_GRAPH_LIMITS.pathLength || !/^[A-Za-z0-9._/-]+$/u.test(value)) fail3();
|
|
2410
|
+
for (const part of value.split("/")) {
|
|
2411
|
+
if (!part || part.length > 255 || part === "." || part === ".." || part.endsWith(".") || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part)) fail3();
|
|
2412
|
+
}
|
|
2413
|
+
return value;
|
|
2414
|
+
}
|
|
2415
|
+
function parseLocation(value) {
|
|
2416
|
+
if (typeof value !== "object" || value === null) fail3();
|
|
2417
|
+
const kind = Object.getOwnPropertyDescriptor(value, "kind");
|
|
2418
|
+
if (!kind || !("value" in kind)) fail3();
|
|
2419
|
+
if (kind.value === "embedded") {
|
|
2420
|
+
record2(value, ["kind"]);
|
|
2421
|
+
return Object.freeze({ kind: "embedded" });
|
|
2422
|
+
}
|
|
2423
|
+
const location = record2(value, ["kind", "relativePath"]);
|
|
2424
|
+
if (location.kind !== "external") fail3();
|
|
2425
|
+
return Object.freeze({ kind: "external", relativePath: relativePath(location.relativePath) });
|
|
2426
|
+
}
|
|
2427
|
+
var graphs = /* @__PURE__ */ new WeakSet();
|
|
2428
|
+
function isGpuModelResourceGraph(value) {
|
|
2429
|
+
return typeof value === "object" && value !== null && graphs.has(value);
|
|
2430
|
+
}
|
|
2431
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2432
|
+
function createGpuModelResourceGraph(document, input) {
|
|
2433
|
+
try {
|
|
2434
|
+
return buildGraph(document, input);
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
if (error instanceof GpuModelResourceGraphError) throw error;
|
|
2437
|
+
fail3();
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function buildGraph(document, input) {
|
|
2441
|
+
if (!isGpuModelDocument(document)) fail3("unverified-document");
|
|
2442
|
+
const raw = record2(input, ["resources", "packages"]);
|
|
2443
|
+
const resourceInputs = array(raw.resources, GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes);
|
|
2444
|
+
const packageInputs = array(raw.packages, GPU_MODEL_RESOURCE_GRAPH_LIMITS.packages);
|
|
2445
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2446
|
+
let edgeCount = 0;
|
|
2447
|
+
const add = (kind, id, value, dependencies) => {
|
|
2448
|
+
const key = gpuModelResourceKey(kind, id);
|
|
2449
|
+
if (nodes.has(key)) fail3("duplicate-reference");
|
|
2450
|
+
if (nodes.size >= GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes) fail3("limit-exceeded");
|
|
2451
|
+
edgeCount += dependencies.length;
|
|
2452
|
+
if (edgeCount > GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges) fail3("limit-exceeded");
|
|
2453
|
+
nodes.set(key, Object.freeze({ kind, id, key, value, dependencies: Object.freeze([...new Set(dependencies)].sort(compare)) }));
|
|
2454
|
+
};
|
|
2455
|
+
const resources = new Map(document.resources.map((resource) => [resource.id, resource]));
|
|
2456
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2457
|
+
for (const entry of resourceInputs) {
|
|
2458
|
+
const binding = record2(entry, ["resourceId", "location", "exportEmbeddingPolicy"]);
|
|
2459
|
+
const id = identifier(binding.resourceId);
|
|
2460
|
+
const resource = resources.get(id);
|
|
2461
|
+
if (!resource) fail3("missing-reference");
|
|
2462
|
+
const location = parseLocation(binding.location);
|
|
2463
|
+
const exportEmbeddingPolicy = choice(binding.exportEmbeddingPolicy, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]);
|
|
2464
|
+
if (location.kind === "embedded" && exportEmbeddingPolicy === "forbid-embedding") fail3();
|
|
2465
|
+
if (location.kind === "external") {
|
|
2466
|
+
const path = location.relativePath.toLowerCase();
|
|
2467
|
+
if (paths.has(path)) fail3("duplicate-reference");
|
|
2468
|
+
paths.add(path);
|
|
2469
|
+
}
|
|
2470
|
+
add("resource", id, Object.freeze({ resource, location, exportEmbeddingPolicy }), []);
|
|
2471
|
+
}
|
|
2472
|
+
for (const path of paths) {
|
|
2473
|
+
for (let index = path.indexOf("/"); index !== -1; index = path.indexOf("/", index + 1)) {
|
|
2474
|
+
if (paths.has(path.slice(0, index))) fail3("duplicate-reference");
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
if (nodes.size !== resources.size) fail3("missing-reference");
|
|
2478
|
+
addDocumentNodes(document, add);
|
|
2479
|
+
for (const entry of packageInputs) {
|
|
2480
|
+
const pkg = record2(entry, ["id", "members"]);
|
|
2481
|
+
const id = identifier(pkg.id);
|
|
2482
|
+
const members = [];
|
|
2483
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2484
|
+
for (const entry2 of array(pkg.members, GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges - edgeCount)) {
|
|
2485
|
+
const reference = record2(entry2, ["kind", "id"]);
|
|
2486
|
+
const member = Object.freeze({ kind: choice(reference.kind, KINDS), id: identifier(reference.id) });
|
|
2487
|
+
const key = gpuModelResourceKey(member.kind, member.id);
|
|
2488
|
+
if (dependencies.has(key)) fail3("duplicate-reference");
|
|
2489
|
+
dependencies.add(key);
|
|
2490
|
+
members.push(member);
|
|
2491
|
+
}
|
|
2492
|
+
members.sort((a, b) => compare(gpuModelResourceKey(a.kind, a.id), gpuModelResourceKey(b.kind, b.id)));
|
|
2493
|
+
add("package", id, Object.freeze({ id, members: Object.freeze(members) }), [...dependencies]);
|
|
2494
|
+
}
|
|
2495
|
+
validateDag(nodes);
|
|
2496
|
+
const result = Object.freeze({ schemaVersion: GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION, nodes: Object.freeze([...nodes.values()].sort((a, b) => compare(a.key, b.key))) });
|
|
2497
|
+
graphs.add(result);
|
|
2498
|
+
return result;
|
|
2499
|
+
}
|
|
2500
|
+
function addDocumentNodes(document, add) {
|
|
2501
|
+
const key = gpuModelResourceKey;
|
|
2502
|
+
for (const value of document.accessors) add("accessor", value.id, value, [key("resource", value.resourceId)]);
|
|
2503
|
+
for (const value of document.textures) add("texture", value.id, value, [key("resource", value.imageResourceId)]);
|
|
2504
|
+
for (const value of document.materials) add("material", value.id, value, Object.values(value.textures).map((binding) => key("texture", binding.textureId)));
|
|
2505
|
+
const joints = new Map(document.joints.map((joint) => [joint.id, joint]));
|
|
2506
|
+
for (const skeleton of document.skeletons) add("skeleton", skeleton.id, Object.freeze({ skeleton, joints: Object.freeze(skeleton.jointIds.map((id) => joints.get(id))) }), []);
|
|
2507
|
+
const primitives = new Map(document.meshes.flatMap((mesh) => mesh.primitives.map((primitive) => [primitive.id, primitive])));
|
|
2508
|
+
for (const value of document.skins) {
|
|
2509
|
+
const dependencies = [key("skeleton", value.skeletonId)];
|
|
2510
|
+
for (const weightSet of value.weightSets) {
|
|
2511
|
+
for (const attribute of primitives.get(weightSet.primitiveId).attributes) {
|
|
2512
|
+
if (/^(JOINTS|WEIGHTS)_/u.test(attribute.semantic)) dependencies.push(key("accessor", attribute.accessorId));
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
add("skin", value.id, value, dependencies);
|
|
2516
|
+
}
|
|
2517
|
+
for (const value of document.animations) add("animation", value.id, value, value.samplers.flatMap((sampler) => [key("accessor", sampler.inputAccessorId), key("accessor", sampler.outputAccessorId)]));
|
|
2518
|
+
add("provenance", "source", document.provenance, []);
|
|
2519
|
+
}
|
|
2520
|
+
function validateDag(nodes) {
|
|
2521
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
2522
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
2523
|
+
const queue = [];
|
|
2524
|
+
for (const node of nodes.values()) {
|
|
2525
|
+
remaining.set(node.key, node.dependencies.length);
|
|
2526
|
+
if (!node.dependencies.length) queue.push(node.key);
|
|
2527
|
+
for (const dependency of node.dependencies) {
|
|
2528
|
+
if (!nodes.has(dependency)) fail3("missing-reference");
|
|
2529
|
+
const users = dependents.get(dependency) ?? [];
|
|
2530
|
+
users.push(node.key);
|
|
2531
|
+
dependents.set(dependency, users);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
for (let index = 0; index < queue.length; index++) {
|
|
2535
|
+
for (const dependent of dependents.get(queue[index]) ?? []) {
|
|
2536
|
+
const count = remaining.get(dependent) - 1;
|
|
2537
|
+
remaining.set(dependent, count);
|
|
2538
|
+
if (!count) queue.push(dependent);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (queue.length !== nodes.size) fail3("graph-cycle");
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// src/adapter-registry.ts
|
|
2545
|
+
var MODEL_REGISTRY_LIMITS = Object.freeze({ adapters: 128, concurrent: 8, timeoutMs: 3e4, maxTimeoutMs: 3e5, string: 4096, mapEntries: 64, manifestEntries: 4096, memoryBytes: 64 * 1024 * 1024 });
|
|
2546
|
+
var ModelRegistryError = class extends TypeError {
|
|
2547
|
+
constructor(code) {
|
|
2548
|
+
super(`GPU model registry: ${code}`);
|
|
2549
|
+
this.code = code;
|
|
2550
|
+
this.name = "ModelRegistryError";
|
|
2551
|
+
}
|
|
2552
|
+
code;
|
|
2553
|
+
};
|
|
2554
|
+
function fail4(code = "invalid-input") {
|
|
2555
|
+
throw new ModelRegistryError(code);
|
|
2556
|
+
}
|
|
2557
|
+
var sourceKinds = ["file-path", "url", "blob-storage-url", "blob", "array-buffer", "uint8-array", "stream"];
|
|
2558
|
+
var targetKinds = ["file-path", "blob-storage-url", "blob", "array-buffer", "uint8-array", "stream", "package"];
|
|
2559
|
+
var bases = /* @__PURE__ */ new WeakMap();
|
|
2560
|
+
function createModelResultBase(input) {
|
|
2561
|
+
const report = evaluateGpuModelDiagnostics(input);
|
|
2562
|
+
const value = Object.freeze({ report, diagnostics: report.diagnostics, warnings: Object.freeze(report.diagnostics.filter((d) => d.severity === "warning")), repairs: report.repairs, lossReports: report.losses });
|
|
2563
|
+
bases.set(report, value);
|
|
2564
|
+
return value;
|
|
2565
|
+
}
|
|
2566
|
+
function record3(value, allowed) {
|
|
2567
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
2568
|
+
const keys = Reflect.ownKeys(value);
|
|
2569
|
+
if (keys.length > MODEL_REGISTRY_LIMITS.mapEntries) fail4();
|
|
2570
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
2571
|
+
for (const key of keys) {
|
|
2572
|
+
if (typeof key !== "string" || allowed && !allowed.includes(key)) fail4();
|
|
2573
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2574
|
+
if (!("value" in descriptor)) fail4();
|
|
2575
|
+
output[key] = descriptor.value;
|
|
2576
|
+
}
|
|
2577
|
+
return output;
|
|
2578
|
+
}
|
|
2579
|
+
function text2(value) {
|
|
2580
|
+
if (typeof value !== "string" || !value.length || value.length > MODEL_REGISTRY_LIMITS.string || Array.from(value).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) fail4();
|
|
2581
|
+
return value;
|
|
2582
|
+
}
|
|
2583
|
+
function format(value) {
|
|
2584
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(value)) fail4();
|
|
2585
|
+
return value;
|
|
2586
|
+
}
|
|
2587
|
+
function enumeration(value, values) {
|
|
2588
|
+
if (!values.includes(value)) fail4();
|
|
2589
|
+
return value;
|
|
2590
|
+
}
|
|
2591
|
+
function boolean2(value) {
|
|
2592
|
+
if (typeof value !== "boolean") fail4();
|
|
2593
|
+
return value;
|
|
2594
|
+
}
|
|
2595
|
+
function integer(value, max, min = 0) {
|
|
2596
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) fail4();
|
|
2597
|
+
return value;
|
|
2598
|
+
}
|
|
2599
|
+
function list(value, max) {
|
|
2600
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length > max || Reflect.ownKeys(value).length !== value.length + 1) fail4();
|
|
2601
|
+
return Array.from({ length: value.length }, (_, i) => {
|
|
2602
|
+
const d = Object.getOwnPropertyDescriptor(value, String(i));
|
|
2603
|
+
if (!d || !("value" in d)) fail4();
|
|
2604
|
+
return d.value;
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
function strings(value) {
|
|
2608
|
+
const input = record3(value);
|
|
2609
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2610
|
+
for (const [key, entry] of Object.entries(input)) out[text2(key)] = text2(entry);
|
|
2611
|
+
return Object.freeze(out);
|
|
2612
|
+
}
|
|
2613
|
+
function metadata(value) {
|
|
2614
|
+
const v = record3(value, ["formatId", "displayName", "supportedSourceKinds", "supportedTargetKinds", "supportsAnimations", "supportsSkins", "supportsAnalyticGeometry"]);
|
|
2615
|
+
const kinds = (input, allowed) => {
|
|
2616
|
+
const values = list(input, allowed.length).map((k) => enumeration(k, allowed));
|
|
2617
|
+
if (new Set(values).size !== values.length) fail4();
|
|
2618
|
+
return Object.freeze(values);
|
|
2619
|
+
};
|
|
2620
|
+
return Object.freeze({ formatId: format(v.formatId), displayName: text2(v.displayName), supportedSourceKinds: kinds(v.supportedSourceKinds, sourceKinds), supportedTargetKinds: kinds(v.supportedTargetKinds, targetKinds), supportsAnimations: boolean2(v.supportsAnimations), supportsSkins: boolean2(v.supportsSkins), supportsAnalyticGeometry: boolean2(v.supportsAnalyticGeometry) });
|
|
2621
|
+
}
|
|
2622
|
+
function snapshotIO(input, source) {
|
|
2623
|
+
const v = record3(input);
|
|
2624
|
+
const kind = enumeration(v.kind, source ? sourceKinds : targetKinds);
|
|
2625
|
+
const common = source ? ["kind", "fileNameHint", "mimeTypeHint", "provenance"] : ["kind"];
|
|
2626
|
+
const specific = source ? {
|
|
2627
|
+
"file-path": ["path"],
|
|
2628
|
+
url: ["url", "headers"],
|
|
2629
|
+
"blob-storage-url": ["url", "credentialMode"],
|
|
2630
|
+
blob: ["blob"],
|
|
2631
|
+
"array-buffer": ["bytes"],
|
|
2632
|
+
"uint8-array": ["bytes"],
|
|
2633
|
+
stream: ["stream", "byteLengthHint"]
|
|
2634
|
+
} : {
|
|
2635
|
+
"file-path": ["path", "overwrite"],
|
|
2636
|
+
"blob-storage-url": ["url", "overwrite", "contentType", "metadata"],
|
|
2637
|
+
blob: ["mimeType", "fileName"],
|
|
2638
|
+
"array-buffer": [],
|
|
2639
|
+
"uint8-array": [],
|
|
2640
|
+
stream: ["stream", "contentType"],
|
|
2641
|
+
package: ["packageFormat", "destinationHint", "includeSourcePayload"]
|
|
2642
|
+
};
|
|
2643
|
+
if (Object.keys(v).some((k) => ![...common, ...specific[kind]].includes(k))) fail4();
|
|
2644
|
+
const out = { kind };
|
|
2645
|
+
for (const key of ["fileNameHint", "mimeTypeHint", "mimeType", "fileName", "contentType", "destinationHint"]) if (v[key] !== void 0) out[key] = text2(v[key]);
|
|
2646
|
+
for (const key of ["overwrite", "includeSourcePayload"]) if (v[key] !== void 0) out[key] = boolean2(v[key]);
|
|
2647
|
+
for (const key of ["headers", "metadata"]) if (v[key] !== void 0) out[key] = strings(v[key]);
|
|
2648
|
+
if (v.provenance !== void 0) out.provenance = strings(record3(v.provenance, ["sourceId", "sourceSystem", "acquiredAt", "licenseHint"]));
|
|
2649
|
+
if (v.credentialMode !== void 0) out.credentialMode = enumeration(v.credentialMode, ["anonymous", "signed", "runtime-resolved"]);
|
|
2650
|
+
if (v.byteLengthHint !== void 0) out.byteLengthHint = integer(v.byteLengthHint, Number.MAX_SAFE_INTEGER);
|
|
2651
|
+
if (kind === "file-path") out.path = text2(v.path);
|
|
2652
|
+
if (kind === "url" || kind === "blob-storage-url") {
|
|
2653
|
+
const url = text2(v.url);
|
|
2654
|
+
let parsed;
|
|
2655
|
+
try {
|
|
2656
|
+
parsed = new URL(url);
|
|
2657
|
+
} catch {
|
|
2658
|
+
return fail4();
|
|
2659
|
+
}
|
|
2660
|
+
if (!["https:", "http:"].includes(parsed.protocol) || parsed.username || parsed.password) fail4();
|
|
2661
|
+
out.url = url;
|
|
2662
|
+
}
|
|
2663
|
+
if (source && kind === "blob") {
|
|
2664
|
+
if (!(v.blob instanceof Blob)) fail4();
|
|
2665
|
+
integer(v.blob.size, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2666
|
+
out.blob = v.blob;
|
|
2667
|
+
}
|
|
2668
|
+
if (source && (kind === "array-buffer" || kind === "uint8-array")) {
|
|
2669
|
+
if (kind === "array-buffer") {
|
|
2670
|
+
if (!(v.bytes instanceof ArrayBuffer)) fail4();
|
|
2671
|
+
integer(v.bytes.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2672
|
+
out.bytes = v.bytes.slice(0);
|
|
2673
|
+
} else {
|
|
2674
|
+
if (!(v.bytes instanceof Uint8Array) || !(v.bytes.buffer instanceof ArrayBuffer)) fail4();
|
|
2675
|
+
integer(v.bytes.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2676
|
+
out.bytes = new Uint8Array(v.bytes);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
if (kind === "stream") {
|
|
2680
|
+
const stream = v.stream;
|
|
2681
|
+
if (!stream || typeof stream !== "object") fail4();
|
|
2682
|
+
if (source ? !(stream instanceof ReadableStream) && typeof stream[Symbol.asyncIterator] !== "function" : !(stream instanceof WritableStream) && !("write" in stream && typeof stream.write === "function" && typeof stream.end === "function")) fail4();
|
|
2683
|
+
out.stream = stream;
|
|
2684
|
+
}
|
|
2685
|
+
if (kind === "package") out.packageFormat = enumeration(v.packageFormat, ["zip", "tar", "directory-manifest"]);
|
|
2686
|
+
return Object.freeze(out);
|
|
2687
|
+
}
|
|
2688
|
+
function manifest(input) {
|
|
2689
|
+
const v = record3(input, ["format", "entries"]);
|
|
2690
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2691
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2692
|
+
const entries = list(v.entries, MODEL_REGISTRY_LIMITS.manifestEntries).map((entry) => {
|
|
2693
|
+
const e = record3(entry, ["id", "role", "path", "contentType"]);
|
|
2694
|
+
const id = text2(e.id);
|
|
2695
|
+
const path = text2(e.path);
|
|
2696
|
+
if (ids.has(id) || paths.has(path.toLowerCase()) || path.split("/").some((s) => !/^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(s) || /[.]$/.test(s) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:[.]|$)/i.test(s))) fail4();
|
|
2697
|
+
ids.add(id);
|
|
2698
|
+
paths.add(path.toLowerCase());
|
|
2699
|
+
return Object.freeze({ id, path, role: enumeration(e.role, ["mesh", "material", "texture", "animation", "auxiliary"]), ...e.contentType !== void 0 ? { contentType: text2(e.contentType) } : {} });
|
|
2700
|
+
});
|
|
2701
|
+
return Object.freeze({ format: enumeration(v.format, ["zip", "tar", "directory-manifest", "inline"]), entries: Object.freeze(entries) });
|
|
2702
|
+
}
|
|
2703
|
+
function reportBase(value, context) {
|
|
2704
|
+
const base = bases.get(value.report);
|
|
2705
|
+
if (!base || base.report.mode !== context.options.mode || ["diagnostics", "warnings", "repairs", "lossReports"].some((k) => value[k] !== base[k])) fail4("invalid-result");
|
|
2706
|
+
return base;
|
|
2707
|
+
}
|
|
2708
|
+
function accepted(report, context) {
|
|
2709
|
+
return report.accepted && (context.options.allowLoss || report.losses.length === 0);
|
|
2710
|
+
}
|
|
2711
|
+
function ledger(stages, ok, payload = {}) {
|
|
2712
|
+
return Object.freeze({ ...payload, accepted: ok, stages: Object.freeze([...stages]), diagnostics: Object.freeze(stages.flatMap((s) => s.report.diagnostics)), warnings: Object.freeze(stages.flatMap((s) => s.report.diagnostics.filter((d) => d.severity === "warning"))), repairs: Object.freeze(stages.flatMap((s) => s.report.repairs)), lossReports: Object.freeze(stages.flatMap((s) => s.report.losses)) });
|
|
2713
|
+
}
|
|
2714
|
+
function createModelConversionRegistry(adapters, limits = {}) {
|
|
2715
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2716
|
+
const maxConcurrent = integer(record3(limits, ["maxConcurrent"]).maxConcurrent ?? MODEL_REGISTRY_LIMITS.concurrent, MODEL_REGISTRY_LIMITS.concurrent, 1);
|
|
2717
|
+
for (const input of list(adapters, MODEL_REGISTRY_LIMITS.adapters)) {
|
|
2718
|
+
let a;
|
|
2719
|
+
try {
|
|
2720
|
+
const v = record3(input, ["metadata", "inspect", "load", "export", "validate"]);
|
|
2721
|
+
const m = metadata(v.metadata);
|
|
2722
|
+
for (const name of ["inspect", "load", "export", "validate"]) if (typeof v[name] !== "function") fail4();
|
|
2723
|
+
const raw = input;
|
|
2724
|
+
a = Object.freeze({ metadata: m, inspect: raw.inspect.bind(raw), load: raw.load.bind(raw), export: raw.export.bind(raw), validate: raw.validate.bind(raw) });
|
|
2725
|
+
} catch {
|
|
2726
|
+
return fail4("invalid-adapter");
|
|
2727
|
+
}
|
|
2728
|
+
if (registry.has(a.metadata.formatId)) fail4("duplicate-format");
|
|
2729
|
+
registry.set(a.metadata.formatId, a);
|
|
2730
|
+
}
|
|
2731
|
+
let active = 0;
|
|
2732
|
+
function lookup(id) {
|
|
2733
|
+
const a = registry.get(format(id));
|
|
2734
|
+
if (!a) fail4("unsupported-format");
|
|
2735
|
+
return a;
|
|
2736
|
+
}
|
|
2737
|
+
function sourceFor(a, input) {
|
|
2738
|
+
const source = snapshotIO(input, true);
|
|
2739
|
+
if (!a.metadata.supportedSourceKinds.includes(source.kind)) fail4("unsupported-source");
|
|
2740
|
+
return source;
|
|
2741
|
+
}
|
|
2742
|
+
function targetFor(a, input) {
|
|
2743
|
+
const target = snapshotIO(input, false);
|
|
2744
|
+
if (!a.metadata.supportedTargetKinds.includes(target.kind)) fail4("unsupported-target");
|
|
2745
|
+
return target;
|
|
2746
|
+
}
|
|
2747
|
+
function check(context) {
|
|
2748
|
+
if (context.options.signal.aborted) fail4("aborted");
|
|
2749
|
+
if (Date.now() >= context.deadline) fail4("deadline-exceeded");
|
|
2750
|
+
}
|
|
2751
|
+
async function run(options, work) {
|
|
2752
|
+
const v = record3(options ?? {}, ["mode", "allowLoss", "signal", "timeoutMs"]);
|
|
2753
|
+
const mode = enumeration(v.mode ?? "strict", ["strict", "tolerant", "forensic"]);
|
|
2754
|
+
const allowLoss = v.allowLoss === void 0 ? false : boolean2(v.allowLoss);
|
|
2755
|
+
const timeoutMs = integer(v.timeoutMs ?? MODEL_REGISTRY_LIMITS.timeoutMs, MODEL_REGISTRY_LIMITS.maxTimeoutMs, 1);
|
|
2756
|
+
if (v.signal !== void 0 && !(v.signal instanceof AbortSignal)) fail4();
|
|
2757
|
+
const parent = v.signal;
|
|
2758
|
+
if (parent?.aborted) fail4("aborted");
|
|
2759
|
+
if (active >= maxConcurrent) fail4("capacity-exceeded");
|
|
2760
|
+
const controller = new AbortController();
|
|
2761
|
+
const context = { options: Object.freeze({ mode, allowLoss, timeoutMs, signal: controller.signal }), deadline: Date.now() + timeoutMs };
|
|
2762
|
+
active++;
|
|
2763
|
+
let timer;
|
|
2764
|
+
let abort;
|
|
2765
|
+
const pending = Promise.resolve().then(() => {
|
|
2766
|
+
check(context);
|
|
2767
|
+
return work(context);
|
|
2768
|
+
}).finally(() => {
|
|
2769
|
+
active--;
|
|
2770
|
+
});
|
|
2771
|
+
const stopped = new Promise((_, reject) => {
|
|
2772
|
+
abort = () => {
|
|
2773
|
+
controller.abort();
|
|
2774
|
+
reject(new ModelRegistryError("aborted"));
|
|
2775
|
+
};
|
|
2776
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
2777
|
+
timer = setTimeout(() => {
|
|
2778
|
+
controller.abort();
|
|
2779
|
+
reject(new ModelRegistryError("deadline-exceeded"));
|
|
2780
|
+
}, timeoutMs);
|
|
2781
|
+
});
|
|
2782
|
+
try {
|
|
2783
|
+
return await Promise.race([pending, stopped]);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (error instanceof ModelRegistryError) throw error;
|
|
2786
|
+
return fail4();
|
|
2787
|
+
} finally {
|
|
2788
|
+
clearTimeout(timer);
|
|
2789
|
+
if (abort) parent?.removeEventListener("abort", abort);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
async function invoke(a, operation, context, call, stages) {
|
|
2793
|
+
check(context);
|
|
2794
|
+
let raw;
|
|
2795
|
+
try {
|
|
2796
|
+
raw = await call();
|
|
2797
|
+
} catch {
|
|
2798
|
+
return fail4("adapter-failed");
|
|
2799
|
+
}
|
|
2800
|
+
check(context);
|
|
2801
|
+
try {
|
|
2802
|
+
const v = record3(raw, ["report", "diagnostics", "warnings", "repairs", "lossReports", ...{ inspect: ["detectedFormat", "capabilities"], load: ["document", "resourcePackage"], validate: ["valid"], export: ["target", "resourcePackage", "output"] }[operation]]);
|
|
2803
|
+
const base = reportBase(v, context);
|
|
2804
|
+
const result = { ...v, ...base };
|
|
2805
|
+
if (v.resourcePackage !== void 0) result.resourcePackage = manifest(v.resourcePackage);
|
|
2806
|
+
stages.push(Object.freeze({ formatId: a.metadata.formatId, operation, report: base.report }));
|
|
2807
|
+
return result;
|
|
2808
|
+
} catch {
|
|
2809
|
+
return fail4("invalid-result");
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
async function load(a, source, c, stages) {
|
|
2813
|
+
const v = await invoke(a, "load", c, () => a.load(source, c.options), stages);
|
|
2814
|
+
if (v.document !== void 0 && !isGpuModelDocument(v.document)) fail4("invalid-result");
|
|
2815
|
+
if (accepted(v.report, c) && !isGpuModelDocument(v.document)) fail4("invalid-result");
|
|
2816
|
+
return Object.freeze(v);
|
|
2817
|
+
}
|
|
2818
|
+
async function validate(a, input, c, stages) {
|
|
2819
|
+
const v = await invoke(a, "validate", c, () => a.validate(input, c.options), stages);
|
|
2820
|
+
if (typeof v.valid !== "boolean") fail4("invalid-result");
|
|
2821
|
+
return v.valid && accepted(v.report, c);
|
|
2822
|
+
}
|
|
2823
|
+
async function exportDocument(a, document, target, c, stages) {
|
|
2824
|
+
const issues = [];
|
|
2825
|
+
for (const [supported, present, path] of [[a.metadata.supportsAnimations, document.animations.length, "/animations"], [a.metadata.supportsSkins, document.skins.length + document.skeletons.length + document.joints.length, "/skins"], [a.metadata.supportsAnalyticGeometry, document.analyticGeometry.length, "/analyticGeometry"]]) {
|
|
2826
|
+
if (!supported && present) issues.push({ kind: "loss", code: "TARGET_CAPABILITY_LOSS", path, message: "Target cannot preserve canonical semantics", effect: "dropped" });
|
|
2827
|
+
}
|
|
2828
|
+
const report = evaluateGpuModelDiagnostics({ standard: "gltf-glb", mode: c.options.mode, allowLoss: c.options.allowLoss, issues, ...c.options.mode === "forensic" ? { rawSource: null } : {} });
|
|
2829
|
+
stages.push(Object.freeze({ formatId: a.metadata.formatId, operation: "capabilities", report }));
|
|
2830
|
+
if (!accepted(report, c) || !await validate(a, document, c, stages)) return ledger(stages, false, { document });
|
|
2831
|
+
const v = await invoke(a, "export", c, () => a.export(document, target, c.options), stages);
|
|
2832
|
+
if (v.target !== target.kind) fail4("invalid-result");
|
|
2833
|
+
try {
|
|
2834
|
+
if (v.output !== void 0) {
|
|
2835
|
+
const output = v.output;
|
|
2836
|
+
if (target.kind === "blob" && output instanceof Blob) {
|
|
2837
|
+
integer(output.size, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2838
|
+
} else if (target.kind === "array-buffer" && output instanceof ArrayBuffer) {
|
|
2839
|
+
integer(output.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2840
|
+
v.output = output.slice(0);
|
|
2841
|
+
} else if (target.kind === "uint8-array" && output instanceof Uint8Array && output.buffer instanceof ArrayBuffer) {
|
|
2842
|
+
integer(output.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2843
|
+
v.output = new Uint8Array(output);
|
|
2844
|
+
} else fail4();
|
|
2845
|
+
}
|
|
2846
|
+
} catch {
|
|
2847
|
+
return fail4("invalid-result");
|
|
2848
|
+
}
|
|
2849
|
+
const exported = Object.freeze(v);
|
|
2850
|
+
return ledger(stages, accepted(exported.report, c), { document, exported, ...exported.resourcePackage ? { resourcePackage: exported.resourcePackage } : {} });
|
|
2851
|
+
}
|
|
2852
|
+
const api = {
|
|
2853
|
+
capabilities: Object.freeze([...registry.values()].map((a) => a.metadata)),
|
|
2854
|
+
inspect: (id, input, options) => run(options, async (c) => {
|
|
2855
|
+
const a = lookup(id);
|
|
2856
|
+
const source = sourceFor(a, input);
|
|
2857
|
+
const stages = [];
|
|
2858
|
+
const v = await invoke(a, "inspect", c, () => a.inspect(source, c.options), stages);
|
|
2859
|
+
try {
|
|
2860
|
+
if (v.detectedFormat !== void 0 && format(v.detectedFormat) !== a.metadata.formatId) fail4();
|
|
2861
|
+
if (JSON.stringify(metadata(v.capabilities)) !== JSON.stringify(a.metadata)) fail4();
|
|
2862
|
+
} catch {
|
|
2863
|
+
return fail4("invalid-result");
|
|
2864
|
+
}
|
|
2865
|
+
return ledger(stages, accepted(v.report, c), { inspection: Object.freeze({ ...v, capabilities: a.metadata }) });
|
|
2866
|
+
}),
|
|
2867
|
+
load: (id, input, options) => run(options, async (c) => {
|
|
2868
|
+
const a = lookup(id);
|
|
2869
|
+
const stages = [];
|
|
2870
|
+
const result = await load(a, sourceFor(a, input), c, stages);
|
|
2871
|
+
return ledger(stages, accepted(result.report, c), { ...result.document ? { document: result.document } : {}, ...result.resourcePackage ? { resourcePackage: result.resourcePackage } : {} });
|
|
2872
|
+
}),
|
|
2873
|
+
validate: (id, input, options) => run(options, async (c) => {
|
|
2874
|
+
const a = lookup(id);
|
|
2875
|
+
const stages = [];
|
|
2876
|
+
return ledger(stages, await validate(a, isGpuModelDocument(input) ? input : sourceFor(a, input), c, stages));
|
|
2877
|
+
}),
|
|
2878
|
+
export: (id, document, input, options) => run(options, async (c) => {
|
|
2879
|
+
const a = lookup(id);
|
|
2880
|
+
if (!isGpuModelDocument(document)) fail4();
|
|
2881
|
+
return exportDocument(a, document, targetFor(a, input), c, []);
|
|
2882
|
+
}),
|
|
2883
|
+
convert: (request, options) => run(options, async (c) => {
|
|
2884
|
+
const v = record3(request, ["sourceFormat", "targetFormat", "source", "target"]);
|
|
2885
|
+
const from = lookup(v.sourceFormat);
|
|
2886
|
+
const to = lookup(v.targetFormat);
|
|
2887
|
+
const source = sourceFor(from, v.source);
|
|
2888
|
+
const target = targetFor(to, v.target);
|
|
2889
|
+
const stages = [];
|
|
2890
|
+
const result = await load(from, source, c, stages);
|
|
2891
|
+
if (!accepted(result.report, c)) return ledger(stages, false, { ...result.resourcePackage ? { resourcePackage: result.resourcePackage } : {} });
|
|
2892
|
+
return exportDocument(to, result.document, target, c, stages);
|
|
2893
|
+
})
|
|
2894
|
+
};
|
|
2895
|
+
return Object.freeze(api);
|
|
2341
2896
|
}
|
|
2342
2897
|
|
|
2343
2898
|
// src/index.ts
|
|
@@ -2354,6 +2909,8 @@ var packageBootstrap = Object.freeze({
|
|
|
2354
2909
|
GPU_MODEL_DOCUMENT_LIMITS,
|
|
2355
2910
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
2356
2911
|
GPU_MODEL_REPAIR_RULES,
|
|
2912
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
2913
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
2357
2914
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
2358
2915
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
2359
2916
|
GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -2364,15 +2921,23 @@ var packageBootstrap = Object.freeze({
|
|
|
2364
2921
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
2365
2922
|
GpuModelDiagnosticsError,
|
|
2366
2923
|
GpuModelDocumentError,
|
|
2924
|
+
GpuModelResourceGraphError,
|
|
2925
|
+
MODEL_REGISTRY_LIMITS,
|
|
2926
|
+
ModelRegistryError,
|
|
2367
2927
|
assertGpuModelStaticDemoNormalization,
|
|
2368
2928
|
canCreateGpuModelDocument,
|
|
2369
2929
|
createAndVerifyGpuModelDocument,
|
|
2370
2930
|
createGpuModelDocument,
|
|
2931
|
+
createGpuModelResourceGraph,
|
|
2371
2932
|
createGpuModelStaticDemoCompilerInput,
|
|
2933
|
+
createModelConversionRegistry,
|
|
2934
|
+
createModelResultBase,
|
|
2372
2935
|
encodeGpuModelDocumentCanonical,
|
|
2373
2936
|
evaluateGpuModelDiagnostics,
|
|
2937
|
+
gpuModelResourceKey,
|
|
2374
2938
|
hashGpuModelDocumentCanonical,
|
|
2375
2939
|
isGpuModelDocument,
|
|
2940
|
+
isGpuModelResourceGraph,
|
|
2376
2941
|
packageBootstrap,
|
|
2377
2942
|
packageName,
|
|
2378
2943
|
verifyGpuModelResource
|