@plasius/gpu-model-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/index.cjs +414 -211
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.js +408 -211
- 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,19 @@ __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,
|
|
38
41
|
assertGpuModelStaticDemoNormalization: () => assertGpuModelStaticDemoNormalization,
|
|
39
42
|
canCreateGpuModelDocument: () => canCreateGpuModelDocument,
|
|
40
43
|
createAndVerifyGpuModelDocument: () => createAndVerifyGpuModelDocument,
|
|
41
44
|
createGpuModelDocument: () => createGpuModelDocument,
|
|
45
|
+
createGpuModelResourceGraph: () => createGpuModelResourceGraph,
|
|
42
46
|
createGpuModelStaticDemoCompilerInput: () => createGpuModelStaticDemoCompilerInput,
|
|
43
47
|
encodeGpuModelDocumentCanonical: () => encodeGpuModelDocumentCanonical,
|
|
44
48
|
evaluateGpuModelDiagnostics: () => evaluateGpuModelDiagnostics,
|
|
49
|
+
gpuModelResourceKey: () => gpuModelResourceKey,
|
|
45
50
|
hashGpuModelDocumentCanonical: () => hashGpuModelDocumentCanonical,
|
|
46
51
|
isGpuModelDocument: () => isGpuModelDocument,
|
|
52
|
+
isGpuModelResourceGraph: () => isGpuModelResourceGraph,
|
|
47
53
|
packageBootstrap: () => packageBootstrap,
|
|
48
54
|
packageName: () => packageName,
|
|
49
55
|
verifyGpuModelResource: () => verifyGpuModelResource
|
|
@@ -224,9 +230,9 @@ function readRecord(value, path, allowedKeys) {
|
|
|
224
230
|
}
|
|
225
231
|
return value;
|
|
226
232
|
}
|
|
227
|
-
function requireKey(
|
|
228
|
-
if (!Object.hasOwn(
|
|
229
|
-
return
|
|
233
|
+
function requireKey(record3, key, path) {
|
|
234
|
+
if (!Object.hasOwn(record3, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
|
|
235
|
+
return record3[key];
|
|
230
236
|
}
|
|
231
237
|
function readArray(value, path, maximum, minimum = 0) {
|
|
232
238
|
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
@@ -305,9 +311,9 @@ function readNumberTuple(value, path, length, minimum, maximum) {
|
|
|
305
311
|
const input = readArray(value, path, length, length);
|
|
306
312
|
return Object.freeze(input.map((entry, index) => readFinite(entry, `${path}[${index}]`, minimum, maximum)));
|
|
307
313
|
}
|
|
308
|
-
function readOptional(
|
|
309
|
-
if (!Object.hasOwn(
|
|
310
|
-
return parser(
|
|
314
|
+
function readOptional(record3, key, parser, path) {
|
|
315
|
+
if (!Object.hasOwn(record3, key)) return void 0;
|
|
316
|
+
return parser(record3[key], `${path}.${key}`);
|
|
311
317
|
}
|
|
312
318
|
function assignOptional(target, key, value) {
|
|
313
319
|
if (value !== void 0) Object.assign(target, { [key]: value });
|
|
@@ -329,17 +335,17 @@ function readMetadataValue(value, path, depth, budget) {
|
|
|
329
335
|
return Object.freeze(input.map((entry, index) => readMetadataValue(entry, `${path}[${index}]`, depth + 1, budget)));
|
|
330
336
|
}
|
|
331
337
|
if (typeof value !== "object" || value === null) return fail("invalid-type", path, "metadata value is not JSON-compatible");
|
|
332
|
-
const
|
|
333
|
-
const prototype = Object.getPrototypeOf(
|
|
338
|
+
const record3 = value;
|
|
339
|
+
const prototype = Object.getPrototypeOf(record3);
|
|
334
340
|
if (prototype !== Object.prototype && prototype !== null) return fail("invalid-type", path, "metadata objects must be plain objects");
|
|
335
|
-
const keys = Reflect.ownKeys(
|
|
341
|
+
const keys = Reflect.ownKeys(record3);
|
|
336
342
|
if (keys.length > GPU_MODEL_DOCUMENT_LIMITS.metadataObjectKeys) return fail("limit-exceeded", path, "metadata object has too many keys");
|
|
337
343
|
const output = {};
|
|
338
344
|
for (const key of keys) {
|
|
339
345
|
if (typeof key !== "string") return fail("unknown-key", path, "metadata symbol keys are not permitted");
|
|
340
346
|
if (PROHIBITED_KEYS.has(key)) return fail("unknown-key", `${path}.${key}`, "prohibited metadata key");
|
|
341
347
|
readString(key, `${path}.[key]`, GPU_MODEL_DOCUMENT_LIMITS.metadataKeyLength, METADATA_KEY);
|
|
342
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
348
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, key);
|
|
343
349
|
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) return fail("invalid-type", `${path}.${key}`, "metadata properties must be enumerable data properties");
|
|
344
350
|
output[key] = readMetadataValue(descriptor.value, `${path}.${key}`, depth + 1, budget);
|
|
345
351
|
}
|
|
@@ -352,9 +358,9 @@ function readMetadata(value, path, budget) {
|
|
|
352
358
|
}
|
|
353
359
|
function parseCoordinateSystem(value) {
|
|
354
360
|
const path = "$.coordinateSystem";
|
|
355
|
-
const
|
|
361
|
+
const record3 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
|
|
356
362
|
for (const [key, expected] of Object.entries(CANONICAL_GPU_MODEL_COORDINATE_SYSTEM)) {
|
|
357
|
-
if (requireKey(
|
|
363
|
+
if (requireKey(record3, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
|
|
358
364
|
}
|
|
359
365
|
return CANONICAL_GPU_MODEL_COORDINATE_SYSTEM;
|
|
360
366
|
}
|
|
@@ -366,19 +372,19 @@ function parseMatrix(value, path, requireAffine = true) {
|
|
|
366
372
|
return matrix;
|
|
367
373
|
}
|
|
368
374
|
function parseBounds(value, path) {
|
|
369
|
-
const
|
|
370
|
-
const min = readNumberTuple(requireKey(
|
|
371
|
-
const max = readNumberTuple(requireKey(
|
|
375
|
+
const record3 = readRecord(value, path, ["min", "max"]);
|
|
376
|
+
const min = readNumberTuple(requireKey(record3, "min", path), `${path}.min`, 3);
|
|
377
|
+
const max = readNumberTuple(requireKey(record3, "max", path), `${path}.max`, 3);
|
|
372
378
|
for (let index = 0; index < 3; index += 1) {
|
|
373
379
|
if (min[index] > max[index]) return fail("invalid-value", path, "bounds min cannot exceed max");
|
|
374
380
|
}
|
|
375
381
|
return { min, max };
|
|
376
382
|
}
|
|
377
383
|
function parseTransformTrs(value, path) {
|
|
378
|
-
const
|
|
379
|
-
const translation = readNumberTuple(requireKey(
|
|
380
|
-
const rotation = readNumberTuple(requireKey(
|
|
381
|
-
const scale = readNumberTuple(requireKey(
|
|
384
|
+
const record3 = readRecord(value, path, ["translation", "rotation", "scale"]);
|
|
385
|
+
const translation = readNumberTuple(requireKey(record3, "translation", path), `${path}.translation`, 3);
|
|
386
|
+
const rotation = readNumberTuple(requireKey(record3, "rotation", path), `${path}.rotation`, 4);
|
|
387
|
+
const scale = readNumberTuple(requireKey(record3, "scale", path), `${path}.scale`, 3);
|
|
382
388
|
const magnitude = Math.hypot(...rotation);
|
|
383
389
|
if (!almostEqual(magnitude, 1, 1e-4)) return fail("invalid-value", `${path}.rotation`, "rotation quaternion must be normalized");
|
|
384
390
|
return { translation, rotation, scale };
|
|
@@ -400,25 +406,25 @@ function intrinsicBlobArrayBuffer(blob) {
|
|
|
400
406
|
return Reflect.apply(blobArrayBuffer, blob, []);
|
|
401
407
|
}
|
|
402
408
|
function parseResourceHeader(value, path) {
|
|
403
|
-
const
|
|
404
|
-
const kind = readEnum(requireKey(
|
|
405
|
-
const mimeType = readString(requireKey(
|
|
409
|
+
const record3 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
|
|
410
|
+
const kind = readEnum(requireKey(record3, "kind", path), `${path}.kind`, ["buffer", "image"]);
|
|
411
|
+
const mimeType = readString(requireKey(record3, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
|
|
406
412
|
if (kind === "image" ? !IMAGE_MIME_TYPES.has(mimeType) : !BUFFER_MIME_TYPES.has(mimeType)) {
|
|
407
413
|
return fail("invalid-value", `${path}.mimeType`, `unsupported ${kind} MIME type`);
|
|
408
414
|
}
|
|
409
|
-
const payload = requireKey(
|
|
415
|
+
const payload = requireKey(record3, "payload", path);
|
|
410
416
|
if (!(payload instanceof Blob)) return fail("invalid-type", `${path}.payload`, "expected an immutable Blob payload");
|
|
411
417
|
if (Object.getPrototypeOf(payload) !== Blob.prototype || Reflect.ownKeys(payload).some((key) => typeof key === "string")) {
|
|
412
418
|
return fail("invalid-type", `${path}.payload`, "expected a native Blob without caller-defined properties");
|
|
413
419
|
}
|
|
414
|
-
const byteLength = readSafeInteger(requireKey(
|
|
420
|
+
const byteLength = readSafeInteger(requireKey(record3, "byteLength", path), `${path}.byteLength`, 1);
|
|
415
421
|
if (intrinsicBlobSize(payload) !== byteLength) return fail("payload-length-mismatch", `${path}.payload`, "payload byte length does not match byteLength");
|
|
416
422
|
const payloadType = intrinsicBlobType(payload);
|
|
417
423
|
if (payloadType && payloadType.toLowerCase() !== mimeType) return fail("invalid-value", `${path}.payload`, "payload MIME type does not match mimeType");
|
|
418
424
|
return {
|
|
419
|
-
id: readId(requireKey(
|
|
425
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
420
426
|
kind,
|
|
421
|
-
contentHash: readSha256(requireKey(
|
|
427
|
+
contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
|
|
422
428
|
byteLength,
|
|
423
429
|
mimeType,
|
|
424
430
|
payload
|
|
@@ -427,11 +433,11 @@ function parseResourceHeader(value, path) {
|
|
|
427
433
|
function resolveVerificationLimits(options) {
|
|
428
434
|
const overrides = options?.limits;
|
|
429
435
|
if (overrides === void 0) return GPU_MODEL_RESOURCE_VERIFICATION_LIMITS;
|
|
430
|
-
const
|
|
436
|
+
const record3 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
|
|
431
437
|
const output = { ...GPU_MODEL_RESOURCE_VERIFICATION_LIMITS };
|
|
432
438
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS)) {
|
|
433
|
-
if (!Object.hasOwn(
|
|
434
|
-
const value = readSafeInteger(
|
|
439
|
+
if (!Object.hasOwn(record3, key)) continue;
|
|
440
|
+
const value = readSafeInteger(record3[key], `$.verification.limits.${key}`, 1, ceiling);
|
|
435
441
|
Object.assign(output, { [key]: value });
|
|
436
442
|
}
|
|
437
443
|
if (output.maxImageBytes > output.maxResourceBytes) output.maxImageBytes = output.maxResourceBytes;
|
|
@@ -543,13 +549,13 @@ function createVerificationDeadline(timeoutMs, callerSignal, path) {
|
|
|
543
549
|
};
|
|
544
550
|
}
|
|
545
551
|
function parseInspection(value, path) {
|
|
546
|
-
const
|
|
552
|
+
const record3 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
|
|
547
553
|
const output = {
|
|
548
|
-
valid: readBoolean(requireKey(
|
|
549
|
-
detectedMimeType: readString(requireKey(
|
|
554
|
+
valid: readBoolean(requireKey(record3, "valid", path), `${path}.valid`),
|
|
555
|
+
detectedMimeType: readString(requireKey(record3, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
|
|
550
556
|
};
|
|
551
|
-
assignOptional(output, "width", readOptional(
|
|
552
|
-
assignOptional(output, "height", readOptional(
|
|
557
|
+
assignOptional(output, "width", readOptional(record3, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
558
|
+
assignOptional(output, "height", readOptional(record3, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
553
559
|
return output;
|
|
554
560
|
}
|
|
555
561
|
function bytesStartWith(bytes, signature) {
|
|
@@ -664,31 +670,31 @@ async function verifyGpuModelResource(value, port, options) {
|
|
|
664
670
|
}
|
|
665
671
|
}
|
|
666
672
|
function parseNode(value, path, budget) {
|
|
667
|
-
const
|
|
668
|
-
const children = Object.freeze(readArray(requireKey(
|
|
673
|
+
const record3 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
|
|
674
|
+
const children = Object.freeze(readArray(requireKey(record3, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
|
|
669
675
|
consumeBudget(budget, "sceneEdges", children.length, GPU_MODEL_DOCUMENT_LIMITS.sceneEdges, path, "scene edge");
|
|
670
676
|
ensureUnique(children, `${path}.children`, "duplicate child node");
|
|
671
677
|
const output = {
|
|
672
|
-
id: readId(requireKey(
|
|
678
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
673
679
|
children,
|
|
674
|
-
localMatrix: parseMatrix(requireKey(
|
|
680
|
+
localMatrix: parseMatrix(requireKey(record3, "localMatrix", path), `${path}.localMatrix`)
|
|
675
681
|
};
|
|
676
|
-
assignOptional(output, "name", readOptional(
|
|
677
|
-
assignOptional(output, "meshId", readOptional(
|
|
678
|
-
assignOptional(output, "skinId", readOptional(
|
|
679
|
-
assignOptional(output, "metadata", readOptional(
|
|
682
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
683
|
+
assignOptional(output, "meshId", readOptional(record3, "meshId", readId, path));
|
|
684
|
+
assignOptional(output, "skinId", readOptional(record3, "skinId", readId, path));
|
|
685
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
680
686
|
return output;
|
|
681
687
|
}
|
|
682
688
|
function parseAccessor(value, path, budget) {
|
|
683
|
-
const
|
|
684
|
-
const componentType = readEnum(requireKey(
|
|
685
|
-
const elementType = readEnum(requireKey(
|
|
686
|
-
const normalized = readOptional(
|
|
689
|
+
const record3 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
|
|
690
|
+
const componentType = readEnum(requireKey(record3, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
|
|
691
|
+
const elementType = readEnum(requireKey(record3, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
|
|
692
|
+
const normalized = readOptional(record3, "normalized", readBoolean, path);
|
|
687
693
|
if (normalized === true && componentType === "f32") return fail("invalid-value", `${path}.normalized`, "floating-point accessors cannot be normalized");
|
|
688
694
|
if (elementType.startsWith("mat") && componentType !== "f32") return fail("invalid-value", `${path}.componentType`, "matrix accessors must use f32 components");
|
|
689
695
|
const expectedComponents = ELEMENT_COMPONENTS[elementType];
|
|
690
|
-
const min = readOptional(
|
|
691
|
-
const max = readOptional(
|
|
696
|
+
const min = readOptional(record3, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
697
|
+
const max = readOptional(record3, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
692
698
|
if (min === void 0 !== (max === void 0)) return fail("invalid-value", path, "accessor min and max must be supplied together");
|
|
693
699
|
if (min && max) {
|
|
694
700
|
const [componentMinimum, componentMaximum, integer] = COMPONENT_RANGES[componentType];
|
|
@@ -701,25 +707,25 @@ function parseAccessor(value, path, budget) {
|
|
|
701
707
|
}
|
|
702
708
|
}
|
|
703
709
|
}
|
|
704
|
-
const count = readSafeInteger(requireKey(
|
|
710
|
+
const count = readSafeInteger(requireKey(record3, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
|
|
705
711
|
consumeBudget(budget, "accessorElements", count * expectedComponents, GPU_MODEL_DOCUMENT_LIMITS.accessorElements, path, "accessor element component");
|
|
706
712
|
const output = {
|
|
707
|
-
id: readId(requireKey(
|
|
708
|
-
resourceId: readId(requireKey(
|
|
709
|
-
byteOffset: readSafeInteger(requireKey(
|
|
713
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
714
|
+
resourceId: readId(requireKey(record3, "resourceId", path), `${path}.resourceId`),
|
|
715
|
+
byteOffset: readSafeInteger(requireKey(record3, "byteOffset", path), `${path}.byteOffset`),
|
|
710
716
|
count,
|
|
711
717
|
componentType,
|
|
712
718
|
elementType
|
|
713
719
|
};
|
|
714
|
-
assignOptional(output, "byteStride", readOptional(
|
|
720
|
+
assignOptional(output, "byteStride", readOptional(record3, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
|
|
715
721
|
assignOptional(output, "normalized", normalized);
|
|
716
722
|
assignOptional(output, "min", min);
|
|
717
723
|
assignOptional(output, "max", max);
|
|
718
724
|
return output;
|
|
719
725
|
}
|
|
720
726
|
function parsePrimitive(value, path, budget) {
|
|
721
|
-
const
|
|
722
|
-
const attributesInput = readArray(requireKey(
|
|
727
|
+
const record3 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
|
|
728
|
+
const attributesInput = readArray(requireKey(record3, "attributes", path), `${path}.attributes`, 64, 1);
|
|
723
729
|
consumeBudget(budget, "primitiveAttributes", attributesInput.length, GPU_MODEL_DOCUMENT_LIMITS.primitiveAttributes, path, "primitive attribute");
|
|
724
730
|
const attributes = Object.freeze(attributesInput.map((entry, index) => {
|
|
725
731
|
const entryPath = `${path}.attributes[${index}]`;
|
|
@@ -732,53 +738,53 @@ function parsePrimitive(value, path, budget) {
|
|
|
732
738
|
ensureUnique(attributes.map(({ semantic }) => semantic), `${path}.attributes`, "duplicate attribute semantic");
|
|
733
739
|
if (!attributes.some(({ semantic }) => semantic === "POSITION")) return fail("invalid-value", `${path}.attributes`, "primitive requires POSITION");
|
|
734
740
|
const output = {
|
|
735
|
-
id: readId(requireKey(
|
|
736
|
-
topology: readEnum(requireKey(
|
|
741
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
742
|
+
topology: readEnum(requireKey(record3, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
|
|
737
743
|
attributes
|
|
738
744
|
};
|
|
739
|
-
assignOptional(output, "indicesAccessorId", readOptional(
|
|
740
|
-
assignOptional(output, "materialId", readOptional(
|
|
741
|
-
assignOptional(output, "metadata", readOptional(
|
|
745
|
+
assignOptional(output, "indicesAccessorId", readOptional(record3, "indicesAccessorId", readId, path));
|
|
746
|
+
assignOptional(output, "materialId", readOptional(record3, "materialId", readId, path));
|
|
747
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
742
748
|
return output;
|
|
743
749
|
}
|
|
744
750
|
function parseMesh(value, path, budget) {
|
|
745
|
-
const
|
|
746
|
-
const input = readArray(requireKey(
|
|
751
|
+
const record3 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
|
|
752
|
+
const input = readArray(requireKey(record3, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
747
753
|
consumeBudget(budget, "primitives", input.length, GPU_MODEL_DOCUMENT_LIMITS.primitives, path, "primitive");
|
|
748
754
|
const output = {
|
|
749
|
-
id: readId(requireKey(
|
|
755
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
750
756
|
primitives: Object.freeze(input.map((entry, index) => parsePrimitive(entry, `${path}.primitives[${index}]`, budget)))
|
|
751
757
|
};
|
|
752
|
-
assignOptional(output, "name", readOptional(
|
|
753
|
-
assignOptional(output, "metadata", readOptional(
|
|
758
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
759
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
754
760
|
return output;
|
|
755
761
|
}
|
|
756
762
|
function parseTextureTransform(value, path) {
|
|
757
|
-
const
|
|
758
|
-
if (Reflect.ownKeys(
|
|
763
|
+
const record3 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
|
|
764
|
+
if (Reflect.ownKeys(record3).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
|
|
759
765
|
const output = {};
|
|
760
|
-
assignOptional(output, "offset", readOptional(
|
|
761
|
-
assignOptional(output, "rotationRadians", readOptional(
|
|
762
|
-
assignOptional(output, "scale", readOptional(
|
|
766
|
+
assignOptional(output, "offset", readOptional(record3, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
767
|
+
assignOptional(output, "rotationRadians", readOptional(record3, "rotationRadians", readFinite, path));
|
|
768
|
+
assignOptional(output, "scale", readOptional(record3, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
763
769
|
return output;
|
|
764
770
|
}
|
|
765
771
|
function parseTextureBinding(value, path) {
|
|
766
|
-
const
|
|
772
|
+
const record3 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
|
|
767
773
|
const output = {
|
|
768
|
-
textureId: readId(requireKey(
|
|
769
|
-
colorSpace: readEnum(requireKey(
|
|
770
|
-
intendedUsage: readEnum(requireKey(
|
|
774
|
+
textureId: readId(requireKey(record3, "textureId", path), `${path}.textureId`),
|
|
775
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
776
|
+
intendedUsage: readEnum(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
|
|
771
777
|
};
|
|
772
|
-
assignOptional(output, "samplerId", readOptional(
|
|
773
|
-
assignOptional(output, "texCoordSet", readOptional(
|
|
774
|
-
assignOptional(output, "transform", readOptional(
|
|
778
|
+
assignOptional(output, "samplerId", readOptional(record3, "samplerId", readId, path));
|
|
779
|
+
assignOptional(output, "texCoordSet", readOptional(record3, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
|
|
780
|
+
assignOptional(output, "transform", readOptional(record3, "transform", parseTextureTransform, path));
|
|
775
781
|
return output;
|
|
776
782
|
}
|
|
777
783
|
function parseMaterialTextures(value, path) {
|
|
778
784
|
const slots = Object.keys(MATERIAL_TEXTURE_SLOTS);
|
|
779
|
-
const
|
|
785
|
+
const record3 = readRecord(value, path, slots);
|
|
780
786
|
const output = {};
|
|
781
|
-
for (const slot of slots) assignOptional(output, slot, readOptional(
|
|
787
|
+
for (const slot of slots) assignOptional(output, slot, readOptional(record3, slot, parseTextureBinding, path));
|
|
782
788
|
return output;
|
|
783
789
|
}
|
|
784
790
|
function parseMaterial(value, path, budget) {
|
|
@@ -804,88 +810,88 @@ function parseMaterial(value, path, budget) {
|
|
|
804
810
|
"extensions",
|
|
805
811
|
"sourceMetadata"
|
|
806
812
|
];
|
|
807
|
-
const
|
|
808
|
-
const alphaMode = readEnum(requireKey(
|
|
809
|
-
const alphaCutoff = readOptional(
|
|
813
|
+
const record3 = readRecord(value, path, keys);
|
|
814
|
+
const alphaMode = readEnum(requireKey(record3, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
|
|
815
|
+
const alphaCutoff = readOptional(record3, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
|
|
810
816
|
if (alphaMode === "mask" && alphaCutoff === void 0) return fail("invalid-value", `${path}.alphaCutoff`, "mask materials require alphaCutoff");
|
|
811
817
|
if (alphaMode !== "mask" && alphaCutoff !== void 0) return fail("invalid-value", `${path}.alphaCutoff`, "alphaCutoff is only valid for mask materials");
|
|
812
818
|
const output = {
|
|
813
|
-
id: readId(requireKey(
|
|
814
|
-
workflow: readEnum(requireKey(
|
|
819
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
820
|
+
workflow: readEnum(requireKey(record3, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
|
|
815
821
|
alphaMode,
|
|
816
|
-
baseColorFactor: readNumberTuple(requireKey(
|
|
817
|
-
textures: parseMaterialTextures(requireKey(
|
|
818
|
-
extensions: readMetadata(requireKey(
|
|
819
|
-
sourceMetadata: readMetadata(requireKey(
|
|
822
|
+
baseColorFactor: readNumberTuple(requireKey(record3, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
|
|
823
|
+
textures: parseMaterialTextures(requireKey(record3, "textures", path), `${path}.textures`),
|
|
824
|
+
extensions: readMetadata(requireKey(record3, "extensions", path), `${path}.extensions`, budget),
|
|
825
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
820
826
|
};
|
|
821
|
-
assignOptional(output, "name", readOptional(
|
|
827
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
822
828
|
assignOptional(output, "alphaCutoff", alphaCutoff);
|
|
823
|
-
assignOptional(output, "doubleSided", readOptional(
|
|
829
|
+
assignOptional(output, "doubleSided", readOptional(record3, "doubleSided", readBoolean, path));
|
|
824
830
|
for (const key of ["metallicFactor", "roughnessFactor", "transmissionFactor", "clearcoatFactor", "clearcoatRoughnessFactor", "sheenRoughnessFactor", "occlusionStrength"]) {
|
|
825
|
-
assignOptional(output, key, readOptional(
|
|
831
|
+
assignOptional(output, key, readOptional(record3, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
826
832
|
}
|
|
827
|
-
assignOptional(output, "normalScale", readOptional(
|
|
828
|
-
assignOptional(output, "emissiveFactor", readOptional(
|
|
829
|
-
assignOptional(output, "sheenColorFactor", readOptional(
|
|
833
|
+
assignOptional(output, "normalScale", readOptional(record3, "normalScale", readFinite, path));
|
|
834
|
+
assignOptional(output, "emissiveFactor", readOptional(record3, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
|
|
835
|
+
assignOptional(output, "sheenColorFactor", readOptional(record3, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
|
|
830
836
|
return output;
|
|
831
837
|
}
|
|
832
838
|
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(
|
|
839
|
+
const record3 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
|
|
840
|
+
const output = { id: readId(requireKey(record3, "id", path), `${path}.id`) };
|
|
841
|
+
assignOptional(output, "wrapS", readOptional(record3, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
842
|
+
assignOptional(output, "wrapT", readOptional(record3, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
843
|
+
assignOptional(output, "minFilter", readOptional(record3, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
844
|
+
assignOptional(output, "magFilter", readOptional(record3, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
845
|
+
assignOptional(output, "mipFilter", readOptional(record3, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
|
|
840
846
|
return output;
|
|
841
847
|
}
|
|
842
848
|
function parseTexture(value, path, budget) {
|
|
843
|
-
const
|
|
844
|
-
const usages = Object.freeze(readArray(requireKey(
|
|
849
|
+
const record3 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
|
|
850
|
+
const usages = Object.freeze(readArray(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
|
|
845
851
|
ensureUnique(usages, `${path}.intendedUsage`, "duplicate intended texture usage");
|
|
846
852
|
const output = {
|
|
847
|
-
id: readId(requireKey(
|
|
848
|
-
contentHash: readSha256(requireKey(
|
|
849
|
-
imageResourceId: readId(requireKey(
|
|
853
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
854
|
+
contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
|
|
855
|
+
imageResourceId: readId(requireKey(record3, "imageResourceId", path), `${path}.imageResourceId`),
|
|
850
856
|
intendedUsage: usages,
|
|
851
|
-
colorSpace: readEnum(requireKey(
|
|
852
|
-
exportEmbeddingPolicy: readEnum(requireKey(
|
|
853
|
-
sourceMetadata: readMetadata(requireKey(
|
|
857
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
858
|
+
exportEmbeddingPolicy: readEnum(requireKey(record3, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
|
|
859
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
854
860
|
};
|
|
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(
|
|
861
|
+
assignOptional(output, "fileName", readOptional(record3, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
|
|
862
|
+
assignOptional(output, "relativePath", readOptional(record3, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
|
|
863
|
+
assignOptional(output, "mimeType", readOptional(record3, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
|
|
864
|
+
assignOptional(output, "byteLength", readOptional(record3, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
865
|
+
assignOptional(output, "sampler", readOptional(record3, "sampler", parseTextureSampler, path));
|
|
860
866
|
return output;
|
|
861
867
|
}
|
|
862
868
|
function parseSkeleton(value, path, budget) {
|
|
863
|
-
const
|
|
864
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
865
|
-
const rootJointIds = Object.freeze(readArray(requireKey(
|
|
869
|
+
const record3 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
|
|
870
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
871
|
+
const rootJointIds = Object.freeze(readArray(requireKey(record3, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
|
|
866
872
|
consumeBudget(budget, "skinJoints", jointIds.length + rootJointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "rig joint");
|
|
867
873
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skeleton joint");
|
|
868
874
|
ensureUnique(rootJointIds, `${path}.rootJointIds`, "duplicate root joint");
|
|
869
875
|
const output = {
|
|
870
|
-
id: readId(requireKey(
|
|
876
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
871
877
|
jointIds,
|
|
872
878
|
rootJointIds,
|
|
873
|
-
sourceMetadata: readMetadata(requireKey(
|
|
879
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
874
880
|
};
|
|
875
|
-
assignOptional(output, "name", readOptional(
|
|
881
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
876
882
|
return output;
|
|
877
883
|
}
|
|
878
884
|
function parseJoint(value, path, budget) {
|
|
879
|
-
const
|
|
885
|
+
const record3 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
|
|
880
886
|
const output = {
|
|
881
|
-
id: readId(requireKey(
|
|
882
|
-
nodeId: readId(requireKey(
|
|
883
|
-
restLocalTransform: parseTransformTrs(requireKey(
|
|
884
|
-
sourceMetadata: readMetadata(requireKey(
|
|
887
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
888
|
+
nodeId: readId(requireKey(record3, "nodeId", path), `${path}.nodeId`),
|
|
889
|
+
restLocalTransform: parseTransformTrs(requireKey(record3, "restLocalTransform", path), `${path}.restLocalTransform`),
|
|
890
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
885
891
|
};
|
|
886
|
-
assignOptional(output, "name", readOptional(
|
|
887
|
-
assignOptional(output, "parentJointId", readOptional(
|
|
888
|
-
assignOptional(output, "inverseBindMatrix", readOptional(
|
|
892
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
893
|
+
assignOptional(output, "parentJointId", readOptional(record3, "parentJointId", readId, path));
|
|
894
|
+
assignOptional(output, "inverseBindMatrix", readOptional(record3, "inverseBindMatrix", parseMatrix, path));
|
|
889
895
|
return output;
|
|
890
896
|
}
|
|
891
897
|
function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
@@ -899,43 +905,43 @@ function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
|
899
905
|
}));
|
|
900
906
|
}
|
|
901
907
|
function parseVertexWeightSet(value, path, budget) {
|
|
902
|
-
const
|
|
903
|
-
const influences = readSafeInteger(requireKey(
|
|
908
|
+
const record3 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
|
|
909
|
+
const influences = readSafeInteger(requireKey(record3, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
|
|
904
910
|
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(
|
|
911
|
+
const jointRows = readArray(requireKey(record3, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
912
|
+
const weightRows = readArray(requireKey(record3, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
907
913
|
if (jointRows.length !== weightRows.length) return fail("invalid-value", path, "joint and weight row counts must match");
|
|
908
914
|
return {
|
|
909
|
-
primitiveId: readId(requireKey(
|
|
915
|
+
primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`),
|
|
910
916
|
influencesPerVertex: influences,
|
|
911
917
|
joints: parseWeightRows(jointRows, `${path}.joints`, jointRows.length, influences, true, budget),
|
|
912
918
|
weights: parseWeightRows(weightRows, `${path}.weights`, weightRows.length, influences, false, budget)
|
|
913
919
|
};
|
|
914
920
|
}
|
|
915
921
|
function parseSkin(value, path, budget) {
|
|
916
|
-
const
|
|
917
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
922
|
+
const record3 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
|
|
923
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
918
924
|
consumeBudget(budget, "skinJoints", jointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "skin joint");
|
|
919
925
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skin joint");
|
|
920
|
-
const inverseInput = readArray(requireKey(
|
|
926
|
+
const inverseInput = readArray(requireKey(record3, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
|
|
921
927
|
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(
|
|
928
|
+
const weightInput = readArray(requireKey(record3, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
923
929
|
const weightSets = Object.freeze(weightInput.map((entry, index) => parseVertexWeightSet(entry, `${path}.weightSets[${index}]`, budget)));
|
|
924
930
|
ensureUnique(weightSets.map(({ primitiveId }) => primitiveId), `${path}.weightSets`, "duplicate primitive weight set");
|
|
925
931
|
return {
|
|
926
|
-
id: readId(requireKey(
|
|
927
|
-
skeletonId: readId(requireKey(
|
|
932
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
933
|
+
skeletonId: readId(requireKey(record3, "skeletonId", path), `${path}.skeletonId`),
|
|
928
934
|
jointIds,
|
|
929
935
|
inverseBindMatrices: Object.freeze(inverseInput.map((entry, index) => parseMatrix(entry, `${path}.inverseBindMatrices[${index}]`))),
|
|
930
936
|
weightSets,
|
|
931
|
-
sourceMetadata: readMetadata(requireKey(
|
|
937
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
932
938
|
};
|
|
933
939
|
}
|
|
934
940
|
function parseBlendShapeDelta(value, path) {
|
|
935
|
-
const
|
|
936
|
-
const output = { primitiveId: readId(requireKey(
|
|
941
|
+
const record3 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
|
|
942
|
+
const output = { primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`) };
|
|
937
943
|
for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
|
|
938
|
-
assignOptional(output, key, readOptional(
|
|
944
|
+
assignOptional(output, key, readOptional(record3, key, readId, path));
|
|
939
945
|
}
|
|
940
946
|
if (!output.positionAccessorId && !output.normalAccessorId && !output.tangentAccessorId) {
|
|
941
947
|
return fail("invalid-value", path, "blend-shape delta requires at least one accessor");
|
|
@@ -943,8 +949,8 @@ function parseBlendShapeDelta(value, path) {
|
|
|
943
949
|
return output;
|
|
944
950
|
}
|
|
945
951
|
function parseBlendShape(value, path, budget) {
|
|
946
|
-
const
|
|
947
|
-
const deltaInput = readArray(requireKey(
|
|
952
|
+
const record3 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
|
|
953
|
+
const deltaInput = readArray(requireKey(record3, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
948
954
|
consumeBudget(
|
|
949
955
|
budget,
|
|
950
956
|
"blendShapeDeltas",
|
|
@@ -956,96 +962,96 @@ function parseBlendShape(value, path, budget) {
|
|
|
956
962
|
const deltas = Object.freeze(deltaInput.map((entry, index) => parseBlendShapeDelta(entry, `${path}.deltas[${index}]`)));
|
|
957
963
|
ensureUnique(deltas.map(({ primitiveId }) => primitiveId), `${path}.deltas`, "duplicate blend-shape primitive delta");
|
|
958
964
|
const output = {
|
|
959
|
-
id: readId(requireKey(
|
|
960
|
-
meshId: readId(requireKey(
|
|
965
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
966
|
+
meshId: readId(requireKey(record3, "meshId", path), `${path}.meshId`),
|
|
961
967
|
deltas,
|
|
962
|
-
sourceMetadata: readMetadata(requireKey(
|
|
968
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
963
969
|
};
|
|
964
|
-
assignOptional(output, "name", readOptional(
|
|
965
|
-
assignOptional(output, "defaultWeight", readOptional(
|
|
970
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
971
|
+
assignOptional(output, "defaultWeight", readOptional(record3, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
966
972
|
return output;
|
|
967
973
|
}
|
|
968
974
|
function parseAnimationSampler(value, path, budget) {
|
|
969
|
-
const
|
|
970
|
-
const timesInput = readArray(requireKey(
|
|
971
|
-
const valuesInput = readArray(requireKey(
|
|
975
|
+
const record3 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
|
|
976
|
+
const timesInput = readArray(requireKey(record3, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
977
|
+
const valuesInput = readArray(requireKey(record3, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
972
978
|
consumeBudget(budget, "animationValues", timesInput.length + valuesInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationValues, path, "animation value");
|
|
973
979
|
const times = Object.freeze(timesInput.map((entry, index) => readFinite(entry, `${path}.inputTimesSeconds[${index}]`, 0)));
|
|
974
980
|
for (let index = 1; index < times.length; index += 1) {
|
|
975
981
|
if (times[index] <= times[index - 1]) return fail("invalid-value", `${path}.inputTimesSeconds`, "animation input times must be strictly increasing");
|
|
976
982
|
}
|
|
977
983
|
return {
|
|
978
|
-
id: readId(requireKey(
|
|
979
|
-
inputAccessorId: readId(requireKey(
|
|
980
|
-
outputAccessorId: readId(requireKey(
|
|
984
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
985
|
+
inputAccessorId: readId(requireKey(record3, "inputAccessorId", path), `${path}.inputAccessorId`),
|
|
986
|
+
outputAccessorId: readId(requireKey(record3, "outputAccessorId", path), `${path}.outputAccessorId`),
|
|
981
987
|
inputTimesSeconds: times,
|
|
982
988
|
outputValues: Object.freeze(valuesInput.map((entry, index) => readFinite(entry, `${path}.outputValues[${index}]`))),
|
|
983
|
-
interpolation: readEnum(requireKey(
|
|
984
|
-
sourceMetadata: readMetadata(requireKey(
|
|
989
|
+
interpolation: readEnum(requireKey(record3, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
|
|
990
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
985
991
|
};
|
|
986
992
|
}
|
|
987
993
|
function parseAnimationChannel(value, path, budget) {
|
|
988
|
-
const
|
|
994
|
+
const record3 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
|
|
989
995
|
return {
|
|
990
|
-
id: readId(requireKey(
|
|
991
|
-
samplerId: readId(requireKey(
|
|
992
|
-
targetType: readEnum(requireKey(
|
|
993
|
-
targetId: readId(requireKey(
|
|
994
|
-
sourceMetadata: readMetadata(requireKey(
|
|
996
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
997
|
+
samplerId: readId(requireKey(record3, "samplerId", path), `${path}.samplerId`),
|
|
998
|
+
targetType: readEnum(requireKey(record3, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
|
|
999
|
+
targetId: readId(requireKey(record3, "targetId", path), `${path}.targetId`),
|
|
1000
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
995
1001
|
};
|
|
996
1002
|
}
|
|
997
1003
|
function parseAnimation(value, path, budget) {
|
|
998
|
-
const
|
|
999
|
-
const samplerInput = readArray(requireKey(
|
|
1000
|
-
const channelInput = readArray(requireKey(
|
|
1004
|
+
const record3 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
|
|
1005
|
+
const samplerInput = readArray(requireKey(record3, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
|
|
1006
|
+
const channelInput = readArray(requireKey(record3, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
|
|
1001
1007
|
consumeBudget(budget, "animationSamplers", samplerInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, path, "animation sampler");
|
|
1002
1008
|
consumeBudget(budget, "animationChannels", channelInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, path, "animation channel");
|
|
1003
1009
|
const output = {
|
|
1004
|
-
id: readId(requireKey(
|
|
1005
|
-
durationSeconds: readFinite(requireKey(
|
|
1010
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
1011
|
+
durationSeconds: readFinite(requireKey(record3, "durationSeconds", path), `${path}.durationSeconds`, 0),
|
|
1006
1012
|
samplers: Object.freeze(samplerInput.map((entry, index) => parseAnimationSampler(entry, `${path}.samplers[${index}]`, budget))),
|
|
1007
1013
|
channels: Object.freeze(channelInput.map((entry, index) => parseAnimationChannel(entry, `${path}.channels[${index}]`, budget))),
|
|
1008
|
-
sourceMetadata: readMetadata(requireKey(
|
|
1014
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
1009
1015
|
};
|
|
1010
|
-
assignOptional(output, "name", readOptional(
|
|
1016
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
1011
1017
|
return output;
|
|
1012
1018
|
}
|
|
1013
1019
|
function parseAnalyticGeometry(value, path, budget) {
|
|
1014
|
-
const
|
|
1015
|
-
if (requireKey(
|
|
1020
|
+
const record3 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
|
|
1021
|
+
if (requireKey(record3, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
|
|
1016
1022
|
const output = {
|
|
1017
|
-
id: readId(requireKey(
|
|
1023
|
+
id: readId(requireKey(record3, "id", path), `${path}.id`),
|
|
1018
1024
|
kind: "brep-placeholder",
|
|
1019
|
-
sourceResourceId: readId(requireKey(
|
|
1020
|
-
sourceUnit: readEnum(requireKey(
|
|
1025
|
+
sourceResourceId: readId(requireKey(record3, "sourceResourceId", path), `${path}.sourceResourceId`),
|
|
1026
|
+
sourceUnit: readEnum(requireKey(record3, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
|
|
1021
1027
|
};
|
|
1022
|
-
assignOptional(output, "tessellatedMeshId", readOptional(
|
|
1023
|
-
assignOptional(output, "metadata", readOptional(
|
|
1028
|
+
assignOptional(output, "tessellatedMeshId", readOptional(record3, "tessellatedMeshId", readId, path));
|
|
1029
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1024
1030
|
return output;
|
|
1025
1031
|
}
|
|
1026
1032
|
function parseProvenance(value, budget) {
|
|
1027
1033
|
const path = "$.provenance";
|
|
1028
|
-
const
|
|
1034
|
+
const record3 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
|
|
1029
1035
|
const output = {
|
|
1030
|
-
sourceFormat: readString(requireKey(
|
|
1031
|
-
sourceContentHash: readSha256(requireKey(
|
|
1032
|
-
converterId: readString(requireKey(
|
|
1033
|
-
converterVersion: readString(requireKey(
|
|
1036
|
+
sourceFormat: readString(requireKey(record3, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
|
|
1037
|
+
sourceContentHash: readSha256(requireKey(record3, "sourceContentHash", path), `${path}.sourceContentHash`),
|
|
1038
|
+
converterId: readString(requireKey(record3, "converterId", path), `${path}.converterId`, 128, TOKEN),
|
|
1039
|
+
converterVersion: readString(requireKey(record3, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
|
|
1034
1040
|
};
|
|
1035
|
-
assignOptional(output, "provider", readOptional(
|
|
1036
|
-
assignOptional(output, "sourceIdentifier", readOptional(
|
|
1037
|
-
assignOptional(output, "metadata", readOptional(
|
|
1041
|
+
assignOptional(output, "provider", readOptional(record3, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
1042
|
+
assignOptional(output, "sourceIdentifier", readOptional(record3, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
|
|
1043
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1038
1044
|
return output;
|
|
1039
1045
|
}
|
|
1040
1046
|
function parseDiagnostic(value, path, budget) {
|
|
1041
|
-
const
|
|
1047
|
+
const record3 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
|
|
1042
1048
|
const output = {
|
|
1043
|
-
severity: readEnum(requireKey(
|
|
1044
|
-
code: readString(requireKey(
|
|
1045
|
-
message: readString(requireKey(
|
|
1049
|
+
severity: readEnum(requireKey(record3, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
|
|
1050
|
+
code: readString(requireKey(record3, "code", path), `${path}.code`, 128, TOKEN),
|
|
1051
|
+
message: readString(requireKey(record3, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
|
|
1046
1052
|
};
|
|
1047
|
-
assignOptional(output, "path", readOptional(
|
|
1048
|
-
assignOptional(output, "metadata", readOptional(
|
|
1053
|
+
assignOptional(output, "path", readOptional(record3, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
|
|
1054
|
+
assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
1049
1055
|
return output;
|
|
1050
1056
|
}
|
|
1051
1057
|
function ensureUnique(values, path, detail = "duplicate identity") {
|
|
@@ -1686,11 +1692,11 @@ var DOCUMENT_KEYS = Object.freeze([
|
|
|
1686
1692
|
]);
|
|
1687
1693
|
function parseGpuModelDocument(value) {
|
|
1688
1694
|
const path = "$";
|
|
1689
|
-
const
|
|
1690
|
-
const schemaVersion = requireKey(
|
|
1695
|
+
const record3 = readRecord(value, path, DOCUMENT_KEYS);
|
|
1696
|
+
const schemaVersion = requireKey(record3, "schemaVersion", path);
|
|
1691
1697
|
if (schemaVersion !== GPU_MODEL_DOCUMENT_SCHEMA_VERSION) return fail("invalid-value", "$.schemaVersion", "unsupported schema version");
|
|
1692
1698
|
const parseCollection = (key, maximum, parser, minimum = 0) => {
|
|
1693
|
-
const input = readArray(requireKey(
|
|
1699
|
+
const input = readArray(requireKey(record3, key, path), `$.${key}`, maximum, minimum);
|
|
1694
1700
|
return Object.freeze(input.map((entry, index) => parser(entry, `$.${key}[${index}]`)));
|
|
1695
1701
|
};
|
|
1696
1702
|
const budget = {
|
|
@@ -1711,10 +1717,10 @@ function parseGpuModelDocument(value) {
|
|
|
1711
1717
|
geometryWorldAccessorInstances: 0,
|
|
1712
1718
|
geometryWorldVertices: 0
|
|
1713
1719
|
};
|
|
1714
|
-
const roots = Object.freeze(readArray(requireKey(
|
|
1720
|
+
const roots = Object.freeze(readArray(requireKey(record3, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
|
|
1715
1721
|
const document = {
|
|
1716
1722
|
schemaVersion,
|
|
1717
|
-
coordinateSystem: parseCoordinateSystem(requireKey(
|
|
1723
|
+
coordinateSystem: parseCoordinateSystem(requireKey(record3, "coordinateSystem", path)),
|
|
1718
1724
|
roots,
|
|
1719
1725
|
nodes: parseCollection("nodes", GPU_MODEL_DOCUMENT_LIMITS.nodes, (entry, entryPath) => parseNode(entry, entryPath, budget), 1),
|
|
1720
1726
|
resources: parseCollection("resources", GPU_MODEL_DOCUMENT_LIMITS.resources, parseVerifiedResource, 1),
|
|
@@ -1728,10 +1734,10 @@ function parseGpuModelDocument(value) {
|
|
|
1728
1734
|
blendShapes: parseCollection("blendShapes", GPU_MODEL_DOCUMENT_LIMITS.blendShapes, (entry, entryPath) => parseBlendShape(entry, entryPath, budget)),
|
|
1729
1735
|
animations: parseCollection("animations", GPU_MODEL_DOCUMENT_LIMITS.animations, (entry, entryPath) => parseAnimation(entry, entryPath, budget)),
|
|
1730
1736
|
analyticGeometry: parseCollection("analyticGeometry", GPU_MODEL_DOCUMENT_LIMITS.analyticGeometry, (entry, entryPath) => parseAnalyticGeometry(entry, entryPath, budget)),
|
|
1731
|
-
bounds: parseBounds(requireKey(
|
|
1732
|
-
provenance: parseProvenance(requireKey(
|
|
1737
|
+
bounds: parseBounds(requireKey(record3, "bounds", path), "$.bounds"),
|
|
1738
|
+
provenance: parseProvenance(requireKey(record3, "provenance", path), budget),
|
|
1733
1739
|
diagnostics: parseCollection("diagnostics", GPU_MODEL_DOCUMENT_LIMITS.diagnostics, (entry, entryPath) => parseDiagnostic(entry, entryPath, budget)),
|
|
1734
|
-
metadata: readMetadata(requireKey(
|
|
1740
|
+
metadata: readMetadata(requireKey(record3, "metadata", path), "$.metadata", budget)
|
|
1735
1741
|
};
|
|
1736
1742
|
if (document.meshes.length === 0) return fail("invalid-value", "$.meshes", "v1 document bounds require at least one canonical mesh");
|
|
1737
1743
|
validateReferences(document, budget);
|
|
@@ -1768,8 +1774,8 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1768
1774
|
value.resources.forEach((resource, index) => assertVerifiedResourceEvidence(resource, limits, `$.resources[${index}]`));
|
|
1769
1775
|
return value;
|
|
1770
1776
|
}
|
|
1771
|
-
const
|
|
1772
|
-
const resourceInput = readArray(requireKey(
|
|
1777
|
+
const record3 = readRecord(value, "$", DOCUMENT_KEYS);
|
|
1778
|
+
const resourceInput = readArray(requireKey(record3, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
|
|
1773
1779
|
const parsed = resourceInput.map((entry, index) => {
|
|
1774
1780
|
if (typeof entry === "object" && entry !== null && verifiedResources.has(entry) && verifiedResourceBytes.has(entry)) return entry;
|
|
1775
1781
|
return parseResourceHeader(entry, `$.resources[${index}]`);
|
|
@@ -1782,7 +1788,7 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1782
1788
|
}
|
|
1783
1789
|
const candidate = {};
|
|
1784
1790
|
for (const key of DOCUMENT_KEYS) {
|
|
1785
|
-
if (Object.hasOwn(
|
|
1791
|
+
if (Object.hasOwn(record3, key)) candidate[key] = record3[key];
|
|
1786
1792
|
}
|
|
1787
1793
|
candidate.resources = verified;
|
|
1788
1794
|
return createGpuModelDocument(candidate);
|
|
@@ -1847,11 +1853,11 @@ async function hashGpuModelDocumentCanonical(document) {
|
|
|
1847
1853
|
return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
1848
1854
|
}
|
|
1849
1855
|
function resolveStaticDemoLimits(options) {
|
|
1850
|
-
const
|
|
1851
|
-
const enabled = readBoolean(requireKey(
|
|
1856
|
+
const record3 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
|
|
1857
|
+
const enabled = readBoolean(requireKey(record3, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
|
|
1852
1858
|
if (!enabled) return fail("feature-disabled", "$.options.pvoxModelsEnabled", `${GPU_MODEL_STATIC_DEMO_FEATURE_FLAG} is disabled`);
|
|
1853
|
-
if (!Object.hasOwn(
|
|
1854
|
-
const overrides = readRecord(
|
|
1859
|
+
if (!Object.hasOwn(record3, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
|
|
1860
|
+
const overrides = readRecord(record3.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
|
|
1855
1861
|
const limits = { ...GPU_MODEL_STATIC_DEMO_LIMITS };
|
|
1856
1862
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_STATIC_DEMO_LIMITS)) {
|
|
1857
1863
|
if (Object.hasOwn(overrides, key)) limits[key] = readSafeInteger(overrides[key], `$.options.limits.${key}`, 1, ceiling);
|
|
@@ -2220,15 +2226,15 @@ function snapshot(value, depth, budget, ancestors, memberLimit = GPU_MODEL_DIAGN
|
|
|
2220
2226
|
return value;
|
|
2221
2227
|
}
|
|
2222
2228
|
if (typeof value !== "object" || ancestors.has(value)) fail2();
|
|
2223
|
-
const
|
|
2229
|
+
const array2 = Array.isArray(value);
|
|
2224
2230
|
const prototype = Object.getPrototypeOf(value);
|
|
2225
|
-
if (
|
|
2231
|
+
if (array2 ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
|
|
2226
2232
|
const keys = Reflect.ownKeys(value);
|
|
2227
2233
|
const maxMembers = memberLimit;
|
|
2228
|
-
if (keys.length > maxMembers + (
|
|
2234
|
+
if (keys.length > maxMembers + (array2 ? 1 : 0)) fail2();
|
|
2229
2235
|
ancestors.add(value);
|
|
2230
2236
|
charge(budget, 2);
|
|
2231
|
-
if (
|
|
2237
|
+
if (array2) {
|
|
2232
2238
|
const length = Object.getOwnPropertyDescriptor(value, "length").value;
|
|
2233
2239
|
if (length > maxMembers || keys.length !== length + 1) fail2();
|
|
2234
2240
|
const result2 = [];
|
|
@@ -2340,6 +2346,197 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2340
2346
|
return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2341
2347
|
}
|
|
2342
2348
|
|
|
2349
|
+
// src/resource-graph.ts
|
|
2350
|
+
var GPU_MODEL_RESOURCE_GRAPH_LIMITS = Object.freeze({
|
|
2351
|
+
nodes: 65536,
|
|
2352
|
+
edges: 262144,
|
|
2353
|
+
packages: 256,
|
|
2354
|
+
pathLength: 1024
|
|
2355
|
+
});
|
|
2356
|
+
var GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION = "plasius.gpu-model-resource-graph/1";
|
|
2357
|
+
var KINDS = ["resource", "accessor", "texture", "material", "skeleton", "skin", "animation", "provenance", "package"];
|
|
2358
|
+
var GpuModelResourceGraphError = class extends TypeError {
|
|
2359
|
+
constructor(code) {
|
|
2360
|
+
super(`Invalid GPU model resource graph: ${code}`);
|
|
2361
|
+
this.code = code;
|
|
2362
|
+
this.name = "GpuModelResourceGraphError";
|
|
2363
|
+
}
|
|
2364
|
+
code;
|
|
2365
|
+
};
|
|
2366
|
+
function fail3(code = "invalid-value") {
|
|
2367
|
+
throw new GpuModelResourceGraphError(code);
|
|
2368
|
+
}
|
|
2369
|
+
function identifier(value) {
|
|
2370
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail3();
|
|
2371
|
+
return value;
|
|
2372
|
+
}
|
|
2373
|
+
function choice(value, choices) {
|
|
2374
|
+
if (typeof value !== "string" || !choices.includes(value)) fail3();
|
|
2375
|
+
return value;
|
|
2376
|
+
}
|
|
2377
|
+
function gpuModelResourceKey(kind, id) {
|
|
2378
|
+
return JSON.stringify([choice(kind, KINDS), identifier(id)]);
|
|
2379
|
+
}
|
|
2380
|
+
function record2(value, keys) {
|
|
2381
|
+
if (typeof value !== "object" || value === null || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
2382
|
+
const names = Reflect.ownKeys(value);
|
|
2383
|
+
if (names.length !== keys.length || names.some((key) => typeof key !== "string" || !keys.includes(key))) fail3();
|
|
2384
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2385
|
+
for (const key of keys) {
|
|
2386
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2387
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2388
|
+
result[key] = descriptor.value;
|
|
2389
|
+
}
|
|
2390
|
+
return result;
|
|
2391
|
+
}
|
|
2392
|
+
function array(value, maximum) {
|
|
2393
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail3();
|
|
2394
|
+
if (value.length > maximum) fail3("limit-exceeded");
|
|
2395
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
2396
|
+
const output = [];
|
|
2397
|
+
for (let index = 0; index < value.length; index++) {
|
|
2398
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
2399
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2400
|
+
output.push(descriptor.value);
|
|
2401
|
+
}
|
|
2402
|
+
return output;
|
|
2403
|
+
}
|
|
2404
|
+
function relativePath(value) {
|
|
2405
|
+
if (typeof value !== "string" || value.length > GPU_MODEL_RESOURCE_GRAPH_LIMITS.pathLength || !/^[A-Za-z0-9._/-]+$/u.test(value)) fail3();
|
|
2406
|
+
for (const part of value.split("/")) {
|
|
2407
|
+
if (!part || part.length > 255 || part === "." || part === ".." || part.endsWith(".") || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part)) fail3();
|
|
2408
|
+
}
|
|
2409
|
+
return value;
|
|
2410
|
+
}
|
|
2411
|
+
function parseLocation(value) {
|
|
2412
|
+
if (typeof value !== "object" || value === null) fail3();
|
|
2413
|
+
const kind = Object.getOwnPropertyDescriptor(value, "kind");
|
|
2414
|
+
if (!kind || !("value" in kind)) fail3();
|
|
2415
|
+
if (kind.value === "embedded") {
|
|
2416
|
+
record2(value, ["kind"]);
|
|
2417
|
+
return Object.freeze({ kind: "embedded" });
|
|
2418
|
+
}
|
|
2419
|
+
const location = record2(value, ["kind", "relativePath"]);
|
|
2420
|
+
if (location.kind !== "external") fail3();
|
|
2421
|
+
return Object.freeze({ kind: "external", relativePath: relativePath(location.relativePath) });
|
|
2422
|
+
}
|
|
2423
|
+
var graphs = /* @__PURE__ */ new WeakSet();
|
|
2424
|
+
function isGpuModelResourceGraph(value) {
|
|
2425
|
+
return typeof value === "object" && value !== null && graphs.has(value);
|
|
2426
|
+
}
|
|
2427
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2428
|
+
function createGpuModelResourceGraph(document, input) {
|
|
2429
|
+
try {
|
|
2430
|
+
return buildGraph(document, input);
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
if (error instanceof GpuModelResourceGraphError) throw error;
|
|
2433
|
+
fail3();
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
function buildGraph(document, input) {
|
|
2437
|
+
if (!isGpuModelDocument(document)) fail3("unverified-document");
|
|
2438
|
+
const raw = record2(input, ["resources", "packages"]);
|
|
2439
|
+
const resourceInputs = array(raw.resources, GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes);
|
|
2440
|
+
const packageInputs = array(raw.packages, GPU_MODEL_RESOURCE_GRAPH_LIMITS.packages);
|
|
2441
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2442
|
+
let edgeCount = 0;
|
|
2443
|
+
const add = (kind, id, value, dependencies) => {
|
|
2444
|
+
const key = gpuModelResourceKey(kind, id);
|
|
2445
|
+
if (nodes.has(key)) fail3("duplicate-reference");
|
|
2446
|
+
if (nodes.size >= GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes) fail3("limit-exceeded");
|
|
2447
|
+
edgeCount += dependencies.length;
|
|
2448
|
+
if (edgeCount > GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges) fail3("limit-exceeded");
|
|
2449
|
+
nodes.set(key, Object.freeze({ kind, id, key, value, dependencies: Object.freeze([...new Set(dependencies)].sort(compare)) }));
|
|
2450
|
+
};
|
|
2451
|
+
const resources = new Map(document.resources.map((resource) => [resource.id, resource]));
|
|
2452
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2453
|
+
for (const entry of resourceInputs) {
|
|
2454
|
+
const binding = record2(entry, ["resourceId", "location", "exportEmbeddingPolicy"]);
|
|
2455
|
+
const id = identifier(binding.resourceId);
|
|
2456
|
+
const resource = resources.get(id);
|
|
2457
|
+
if (!resource) fail3("missing-reference");
|
|
2458
|
+
const location = parseLocation(binding.location);
|
|
2459
|
+
const exportEmbeddingPolicy = choice(binding.exportEmbeddingPolicy, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]);
|
|
2460
|
+
if (location.kind === "embedded" && exportEmbeddingPolicy === "forbid-embedding") fail3();
|
|
2461
|
+
if (location.kind === "external") {
|
|
2462
|
+
const path = location.relativePath.toLowerCase();
|
|
2463
|
+
if (paths.has(path)) fail3("duplicate-reference");
|
|
2464
|
+
paths.add(path);
|
|
2465
|
+
}
|
|
2466
|
+
add("resource", id, Object.freeze({ resource, location, exportEmbeddingPolicy }), []);
|
|
2467
|
+
}
|
|
2468
|
+
for (const path of paths) {
|
|
2469
|
+
for (let index = path.indexOf("/"); index !== -1; index = path.indexOf("/", index + 1)) {
|
|
2470
|
+
if (paths.has(path.slice(0, index))) fail3("duplicate-reference");
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
if (nodes.size !== resources.size) fail3("missing-reference");
|
|
2474
|
+
addDocumentNodes(document, add);
|
|
2475
|
+
for (const entry of packageInputs) {
|
|
2476
|
+
const pkg = record2(entry, ["id", "members"]);
|
|
2477
|
+
const id = identifier(pkg.id);
|
|
2478
|
+
const members = [];
|
|
2479
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2480
|
+
for (const entry2 of array(pkg.members, GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges - edgeCount)) {
|
|
2481
|
+
const reference = record2(entry2, ["kind", "id"]);
|
|
2482
|
+
const member = Object.freeze({ kind: choice(reference.kind, KINDS), id: identifier(reference.id) });
|
|
2483
|
+
const key = gpuModelResourceKey(member.kind, member.id);
|
|
2484
|
+
if (dependencies.has(key)) fail3("duplicate-reference");
|
|
2485
|
+
dependencies.add(key);
|
|
2486
|
+
members.push(member);
|
|
2487
|
+
}
|
|
2488
|
+
members.sort((a, b) => compare(gpuModelResourceKey(a.kind, a.id), gpuModelResourceKey(b.kind, b.id)));
|
|
2489
|
+
add("package", id, Object.freeze({ id, members: Object.freeze(members) }), [...dependencies]);
|
|
2490
|
+
}
|
|
2491
|
+
validateDag(nodes);
|
|
2492
|
+
const result = Object.freeze({ schemaVersion: GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION, nodes: Object.freeze([...nodes.values()].sort((a, b) => compare(a.key, b.key))) });
|
|
2493
|
+
graphs.add(result);
|
|
2494
|
+
return result;
|
|
2495
|
+
}
|
|
2496
|
+
function addDocumentNodes(document, add) {
|
|
2497
|
+
const key = gpuModelResourceKey;
|
|
2498
|
+
for (const value of document.accessors) add("accessor", value.id, value, [key("resource", value.resourceId)]);
|
|
2499
|
+
for (const value of document.textures) add("texture", value.id, value, [key("resource", value.imageResourceId)]);
|
|
2500
|
+
for (const value of document.materials) add("material", value.id, value, Object.values(value.textures).map((binding) => key("texture", binding.textureId)));
|
|
2501
|
+
const joints = new Map(document.joints.map((joint) => [joint.id, joint]));
|
|
2502
|
+
for (const skeleton of document.skeletons) add("skeleton", skeleton.id, Object.freeze({ skeleton, joints: Object.freeze(skeleton.jointIds.map((id) => joints.get(id))) }), []);
|
|
2503
|
+
const primitives = new Map(document.meshes.flatMap((mesh) => mesh.primitives.map((primitive) => [primitive.id, primitive])));
|
|
2504
|
+
for (const value of document.skins) {
|
|
2505
|
+
const dependencies = [key("skeleton", value.skeletonId)];
|
|
2506
|
+
for (const weightSet of value.weightSets) {
|
|
2507
|
+
for (const attribute of primitives.get(weightSet.primitiveId).attributes) {
|
|
2508
|
+
if (/^(JOINTS|WEIGHTS)_/u.test(attribute.semantic)) dependencies.push(key("accessor", attribute.accessorId));
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
add("skin", value.id, value, dependencies);
|
|
2512
|
+
}
|
|
2513
|
+
for (const value of document.animations) add("animation", value.id, value, value.samplers.flatMap((sampler) => [key("accessor", sampler.inputAccessorId), key("accessor", sampler.outputAccessorId)]));
|
|
2514
|
+
add("provenance", "source", document.provenance, []);
|
|
2515
|
+
}
|
|
2516
|
+
function validateDag(nodes) {
|
|
2517
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
2518
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
2519
|
+
const queue = [];
|
|
2520
|
+
for (const node of nodes.values()) {
|
|
2521
|
+
remaining.set(node.key, node.dependencies.length);
|
|
2522
|
+
if (!node.dependencies.length) queue.push(node.key);
|
|
2523
|
+
for (const dependency of node.dependencies) {
|
|
2524
|
+
if (!nodes.has(dependency)) fail3("missing-reference");
|
|
2525
|
+
const users = dependents.get(dependency) ?? [];
|
|
2526
|
+
users.push(node.key);
|
|
2527
|
+
dependents.set(dependency, users);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
for (let index = 0; index < queue.length; index++) {
|
|
2531
|
+
for (const dependent of dependents.get(queue[index]) ?? []) {
|
|
2532
|
+
const count = remaining.get(dependent) - 1;
|
|
2533
|
+
remaining.set(dependent, count);
|
|
2534
|
+
if (!count) queue.push(dependent);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (queue.length !== nodes.size) fail3("graph-cycle");
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2343
2540
|
// src/index.ts
|
|
2344
2541
|
var packageName = "@plasius/gpu-model-core";
|
|
2345
2542
|
var packageBootstrap = Object.freeze({
|
|
@@ -2354,6 +2551,8 @@ var packageBootstrap = Object.freeze({
|
|
|
2354
2551
|
GPU_MODEL_DOCUMENT_LIMITS,
|
|
2355
2552
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
2356
2553
|
GPU_MODEL_REPAIR_RULES,
|
|
2554
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
2555
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
2357
2556
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
2358
2557
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
2359
2558
|
GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -2364,15 +2563,19 @@ var packageBootstrap = Object.freeze({
|
|
|
2364
2563
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
2365
2564
|
GpuModelDiagnosticsError,
|
|
2366
2565
|
GpuModelDocumentError,
|
|
2566
|
+
GpuModelResourceGraphError,
|
|
2367
2567
|
assertGpuModelStaticDemoNormalization,
|
|
2368
2568
|
canCreateGpuModelDocument,
|
|
2369
2569
|
createAndVerifyGpuModelDocument,
|
|
2370
2570
|
createGpuModelDocument,
|
|
2571
|
+
createGpuModelResourceGraph,
|
|
2371
2572
|
createGpuModelStaticDemoCompilerInput,
|
|
2372
2573
|
encodeGpuModelDocumentCanonical,
|
|
2373
2574
|
evaluateGpuModelDiagnostics,
|
|
2575
|
+
gpuModelResourceKey,
|
|
2374
2576
|
hashGpuModelDocumentCanonical,
|
|
2375
2577
|
isGpuModelDocument,
|
|
2578
|
+
isGpuModelResourceGraph,
|
|
2376
2579
|
packageBootstrap,
|
|
2377
2580
|
packageName,
|
|
2378
2581
|
verifyGpuModelResource
|