@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.js
CHANGED
|
@@ -172,9 +172,9 @@ function readRecord(value, path, allowedKeys) {
|
|
|
172
172
|
}
|
|
173
173
|
return value;
|
|
174
174
|
}
|
|
175
|
-
function requireKey(
|
|
176
|
-
if (!Object.hasOwn(
|
|
177
|
-
return
|
|
175
|
+
function requireKey(record4, key, path) {
|
|
176
|
+
if (!Object.hasOwn(record4, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
|
|
177
|
+
return record4[key];
|
|
178
178
|
}
|
|
179
179
|
function readArray(value, path, maximum, minimum = 0) {
|
|
180
180
|
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
|
|
@@ -253,9 +253,9 @@ function readNumberTuple(value, path, length, minimum, maximum) {
|
|
|
253
253
|
const input = readArray(value, path, length, length);
|
|
254
254
|
return Object.freeze(input.map((entry, index) => readFinite(entry, `${path}[${index}]`, minimum, maximum)));
|
|
255
255
|
}
|
|
256
|
-
function readOptional(
|
|
257
|
-
if (!Object.hasOwn(
|
|
258
|
-
return parser(
|
|
256
|
+
function readOptional(record4, key, parser, path) {
|
|
257
|
+
if (!Object.hasOwn(record4, key)) return void 0;
|
|
258
|
+
return parser(record4[key], `${path}.${key}`);
|
|
259
259
|
}
|
|
260
260
|
function assignOptional(target, key, value) {
|
|
261
261
|
if (value !== void 0) Object.assign(target, { [key]: value });
|
|
@@ -277,17 +277,17 @@ function readMetadataValue(value, path, depth, budget) {
|
|
|
277
277
|
return Object.freeze(input.map((entry, index) => readMetadataValue(entry, `${path}[${index}]`, depth + 1, budget)));
|
|
278
278
|
}
|
|
279
279
|
if (typeof value !== "object" || value === null) return fail("invalid-type", path, "metadata value is not JSON-compatible");
|
|
280
|
-
const
|
|
281
|
-
const prototype = Object.getPrototypeOf(
|
|
280
|
+
const record4 = value;
|
|
281
|
+
const prototype = Object.getPrototypeOf(record4);
|
|
282
282
|
if (prototype !== Object.prototype && prototype !== null) return fail("invalid-type", path, "metadata objects must be plain objects");
|
|
283
|
-
const keys = Reflect.ownKeys(
|
|
283
|
+
const keys = Reflect.ownKeys(record4);
|
|
284
284
|
if (keys.length > GPU_MODEL_DOCUMENT_LIMITS.metadataObjectKeys) return fail("limit-exceeded", path, "metadata object has too many keys");
|
|
285
285
|
const output = {};
|
|
286
286
|
for (const key of keys) {
|
|
287
287
|
if (typeof key !== "string") return fail("unknown-key", path, "metadata symbol keys are not permitted");
|
|
288
288
|
if (PROHIBITED_KEYS.has(key)) return fail("unknown-key", `${path}.${key}`, "prohibited metadata key");
|
|
289
289
|
readString(key, `${path}.[key]`, GPU_MODEL_DOCUMENT_LIMITS.metadataKeyLength, METADATA_KEY);
|
|
290
|
-
const descriptor = Object.getOwnPropertyDescriptor(
|
|
290
|
+
const descriptor = Object.getOwnPropertyDescriptor(record4, key);
|
|
291
291
|
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) return fail("invalid-type", `${path}.${key}`, "metadata properties must be enumerable data properties");
|
|
292
292
|
output[key] = readMetadataValue(descriptor.value, `${path}.${key}`, depth + 1, budget);
|
|
293
293
|
}
|
|
@@ -300,9 +300,9 @@ function readMetadata(value, path, budget) {
|
|
|
300
300
|
}
|
|
301
301
|
function parseCoordinateSystem(value) {
|
|
302
302
|
const path = "$.coordinateSystem";
|
|
303
|
-
const
|
|
303
|
+
const record4 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
|
|
304
304
|
for (const [key, expected] of Object.entries(CANONICAL_GPU_MODEL_COORDINATE_SYSTEM)) {
|
|
305
|
-
if (requireKey(
|
|
305
|
+
if (requireKey(record4, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
|
|
306
306
|
}
|
|
307
307
|
return CANONICAL_GPU_MODEL_COORDINATE_SYSTEM;
|
|
308
308
|
}
|
|
@@ -314,19 +314,19 @@ function parseMatrix(value, path, requireAffine = true) {
|
|
|
314
314
|
return matrix;
|
|
315
315
|
}
|
|
316
316
|
function parseBounds(value, path) {
|
|
317
|
-
const
|
|
318
|
-
const min = readNumberTuple(requireKey(
|
|
319
|
-
const max = readNumberTuple(requireKey(
|
|
317
|
+
const record4 = readRecord(value, path, ["min", "max"]);
|
|
318
|
+
const min = readNumberTuple(requireKey(record4, "min", path), `${path}.min`, 3);
|
|
319
|
+
const max = readNumberTuple(requireKey(record4, "max", path), `${path}.max`, 3);
|
|
320
320
|
for (let index = 0; index < 3; index += 1) {
|
|
321
321
|
if (min[index] > max[index]) return fail("invalid-value", path, "bounds min cannot exceed max");
|
|
322
322
|
}
|
|
323
323
|
return { min, max };
|
|
324
324
|
}
|
|
325
325
|
function parseTransformTrs(value, path) {
|
|
326
|
-
const
|
|
327
|
-
const translation = readNumberTuple(requireKey(
|
|
328
|
-
const rotation = readNumberTuple(requireKey(
|
|
329
|
-
const scale = readNumberTuple(requireKey(
|
|
326
|
+
const record4 = readRecord(value, path, ["translation", "rotation", "scale"]);
|
|
327
|
+
const translation = readNumberTuple(requireKey(record4, "translation", path), `${path}.translation`, 3);
|
|
328
|
+
const rotation = readNumberTuple(requireKey(record4, "rotation", path), `${path}.rotation`, 4);
|
|
329
|
+
const scale = readNumberTuple(requireKey(record4, "scale", path), `${path}.scale`, 3);
|
|
330
330
|
const magnitude = Math.hypot(...rotation);
|
|
331
331
|
if (!almostEqual(magnitude, 1, 1e-4)) return fail("invalid-value", `${path}.rotation`, "rotation quaternion must be normalized");
|
|
332
332
|
return { translation, rotation, scale };
|
|
@@ -348,25 +348,25 @@ function intrinsicBlobArrayBuffer(blob) {
|
|
|
348
348
|
return Reflect.apply(blobArrayBuffer, blob, []);
|
|
349
349
|
}
|
|
350
350
|
function parseResourceHeader(value, path) {
|
|
351
|
-
const
|
|
352
|
-
const kind = readEnum(requireKey(
|
|
353
|
-
const mimeType = readString(requireKey(
|
|
351
|
+
const record4 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
|
|
352
|
+
const kind = readEnum(requireKey(record4, "kind", path), `${path}.kind`, ["buffer", "image"]);
|
|
353
|
+
const mimeType = readString(requireKey(record4, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
|
|
354
354
|
if (kind === "image" ? !IMAGE_MIME_TYPES.has(mimeType) : !BUFFER_MIME_TYPES.has(mimeType)) {
|
|
355
355
|
return fail("invalid-value", `${path}.mimeType`, `unsupported ${kind} MIME type`);
|
|
356
356
|
}
|
|
357
|
-
const payload = requireKey(
|
|
357
|
+
const payload = requireKey(record4, "payload", path);
|
|
358
358
|
if (!(payload instanceof Blob)) return fail("invalid-type", `${path}.payload`, "expected an immutable Blob payload");
|
|
359
359
|
if (Object.getPrototypeOf(payload) !== Blob.prototype || Reflect.ownKeys(payload).some((key) => typeof key === "string")) {
|
|
360
360
|
return fail("invalid-type", `${path}.payload`, "expected a native Blob without caller-defined properties");
|
|
361
361
|
}
|
|
362
|
-
const byteLength = readSafeInteger(requireKey(
|
|
362
|
+
const byteLength = readSafeInteger(requireKey(record4, "byteLength", path), `${path}.byteLength`, 1);
|
|
363
363
|
if (intrinsicBlobSize(payload) !== byteLength) return fail("payload-length-mismatch", `${path}.payload`, "payload byte length does not match byteLength");
|
|
364
364
|
const payloadType = intrinsicBlobType(payload);
|
|
365
365
|
if (payloadType && payloadType.toLowerCase() !== mimeType) return fail("invalid-value", `${path}.payload`, "payload MIME type does not match mimeType");
|
|
366
366
|
return {
|
|
367
|
-
id: readId(requireKey(
|
|
367
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
368
368
|
kind,
|
|
369
|
-
contentHash: readSha256(requireKey(
|
|
369
|
+
contentHash: readSha256(requireKey(record4, "contentHash", path), `${path}.contentHash`),
|
|
370
370
|
byteLength,
|
|
371
371
|
mimeType,
|
|
372
372
|
payload
|
|
@@ -375,11 +375,11 @@ function parseResourceHeader(value, path) {
|
|
|
375
375
|
function resolveVerificationLimits(options) {
|
|
376
376
|
const overrides = options?.limits;
|
|
377
377
|
if (overrides === void 0) return GPU_MODEL_RESOURCE_VERIFICATION_LIMITS;
|
|
378
|
-
const
|
|
378
|
+
const record4 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
|
|
379
379
|
const output = { ...GPU_MODEL_RESOURCE_VERIFICATION_LIMITS };
|
|
380
380
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS)) {
|
|
381
|
-
if (!Object.hasOwn(
|
|
382
|
-
const value = readSafeInteger(
|
|
381
|
+
if (!Object.hasOwn(record4, key)) continue;
|
|
382
|
+
const value = readSafeInteger(record4[key], `$.verification.limits.${key}`, 1, ceiling);
|
|
383
383
|
Object.assign(output, { [key]: value });
|
|
384
384
|
}
|
|
385
385
|
if (output.maxImageBytes > output.maxResourceBytes) output.maxImageBytes = output.maxResourceBytes;
|
|
@@ -491,13 +491,13 @@ function createVerificationDeadline(timeoutMs, callerSignal, path) {
|
|
|
491
491
|
};
|
|
492
492
|
}
|
|
493
493
|
function parseInspection(value, path) {
|
|
494
|
-
const
|
|
494
|
+
const record4 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
|
|
495
495
|
const output = {
|
|
496
|
-
valid: readBoolean(requireKey(
|
|
497
|
-
detectedMimeType: readString(requireKey(
|
|
496
|
+
valid: readBoolean(requireKey(record4, "valid", path), `${path}.valid`),
|
|
497
|
+
detectedMimeType: readString(requireKey(record4, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
|
|
498
498
|
};
|
|
499
|
-
assignOptional(output, "width", readOptional(
|
|
500
|
-
assignOptional(output, "height", readOptional(
|
|
499
|
+
assignOptional(output, "width", readOptional(record4, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
500
|
+
assignOptional(output, "height", readOptional(record4, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
501
501
|
return output;
|
|
502
502
|
}
|
|
503
503
|
function bytesStartWith(bytes, signature) {
|
|
@@ -612,62 +612,62 @@ async function verifyGpuModelResource(value, port, options) {
|
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
614
|
function parseNode(value, path, budget) {
|
|
615
|
-
const
|
|
616
|
-
const children = Object.freeze(readArray(requireKey(
|
|
615
|
+
const record4 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
|
|
616
|
+
const children = Object.freeze(readArray(requireKey(record4, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
|
|
617
617
|
consumeBudget(budget, "sceneEdges", children.length, GPU_MODEL_DOCUMENT_LIMITS.sceneEdges, path, "scene edge");
|
|
618
618
|
ensureUnique(children, `${path}.children`, "duplicate child node");
|
|
619
619
|
const output = {
|
|
620
|
-
id: readId(requireKey(
|
|
620
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
621
621
|
children,
|
|
622
|
-
localMatrix: parseMatrix(requireKey(
|
|
622
|
+
localMatrix: parseMatrix(requireKey(record4, "localMatrix", path), `${path}.localMatrix`)
|
|
623
623
|
};
|
|
624
|
-
assignOptional(output, "name", readOptional(
|
|
625
|
-
assignOptional(output, "meshId", readOptional(
|
|
626
|
-
assignOptional(output, "skinId", readOptional(
|
|
627
|
-
assignOptional(output, "metadata", readOptional(
|
|
624
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
625
|
+
assignOptional(output, "meshId", readOptional(record4, "meshId", readId, path));
|
|
626
|
+
assignOptional(output, "skinId", readOptional(record4, "skinId", readId, path));
|
|
627
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
628
628
|
return output;
|
|
629
629
|
}
|
|
630
630
|
function parseAccessor(value, path, budget) {
|
|
631
|
-
const
|
|
632
|
-
const componentType = readEnum(requireKey(
|
|
633
|
-
const elementType = readEnum(requireKey(
|
|
634
|
-
const normalized = readOptional(
|
|
631
|
+
const record4 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
|
|
632
|
+
const componentType = readEnum(requireKey(record4, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
|
|
633
|
+
const elementType = readEnum(requireKey(record4, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
|
|
634
|
+
const normalized = readOptional(record4, "normalized", readBoolean, path);
|
|
635
635
|
if (normalized === true && componentType === "f32") return fail("invalid-value", `${path}.normalized`, "floating-point accessors cannot be normalized");
|
|
636
636
|
if (elementType.startsWith("mat") && componentType !== "f32") return fail("invalid-value", `${path}.componentType`, "matrix accessors must use f32 components");
|
|
637
637
|
const expectedComponents = ELEMENT_COMPONENTS[elementType];
|
|
638
|
-
const min = readOptional(
|
|
639
|
-
const max = readOptional(
|
|
638
|
+
const min = readOptional(record4, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
639
|
+
const max = readOptional(record4, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
640
640
|
if (min === void 0 !== (max === void 0)) return fail("invalid-value", path, "accessor min and max must be supplied together");
|
|
641
641
|
if (min && max) {
|
|
642
|
-
const [componentMinimum, componentMaximum,
|
|
642
|
+
const [componentMinimum, componentMaximum, integer2] = COMPONENT_RANGES[componentType];
|
|
643
643
|
for (let index = 0; index < expectedComponents; index += 1) {
|
|
644
644
|
const minimum = min[index];
|
|
645
645
|
const maximum = max[index];
|
|
646
646
|
if (minimum > maximum) return fail("invalid-value", path, "accessor min cannot exceed max");
|
|
647
|
-
if (minimum < componentMinimum || maximum > componentMaximum ||
|
|
647
|
+
if (minimum < componentMinimum || maximum > componentMaximum || integer2 && (!Number.isInteger(minimum) || !Number.isInteger(maximum))) {
|
|
648
648
|
return fail("invalid-value", path, "accessor bounds are outside the component representation");
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
|
-
const count = readSafeInteger(requireKey(
|
|
652
|
+
const count = readSafeInteger(requireKey(record4, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
|
|
653
653
|
consumeBudget(budget, "accessorElements", count * expectedComponents, GPU_MODEL_DOCUMENT_LIMITS.accessorElements, path, "accessor element component");
|
|
654
654
|
const output = {
|
|
655
|
-
id: readId(requireKey(
|
|
656
|
-
resourceId: readId(requireKey(
|
|
657
|
-
byteOffset: readSafeInteger(requireKey(
|
|
655
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
656
|
+
resourceId: readId(requireKey(record4, "resourceId", path), `${path}.resourceId`),
|
|
657
|
+
byteOffset: readSafeInteger(requireKey(record4, "byteOffset", path), `${path}.byteOffset`),
|
|
658
658
|
count,
|
|
659
659
|
componentType,
|
|
660
660
|
elementType
|
|
661
661
|
};
|
|
662
|
-
assignOptional(output, "byteStride", readOptional(
|
|
662
|
+
assignOptional(output, "byteStride", readOptional(record4, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
|
|
663
663
|
assignOptional(output, "normalized", normalized);
|
|
664
664
|
assignOptional(output, "min", min);
|
|
665
665
|
assignOptional(output, "max", max);
|
|
666
666
|
return output;
|
|
667
667
|
}
|
|
668
668
|
function parsePrimitive(value, path, budget) {
|
|
669
|
-
const
|
|
670
|
-
const attributesInput = readArray(requireKey(
|
|
669
|
+
const record4 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
|
|
670
|
+
const attributesInput = readArray(requireKey(record4, "attributes", path), `${path}.attributes`, 64, 1);
|
|
671
671
|
consumeBudget(budget, "primitiveAttributes", attributesInput.length, GPU_MODEL_DOCUMENT_LIMITS.primitiveAttributes, path, "primitive attribute");
|
|
672
672
|
const attributes = Object.freeze(attributesInput.map((entry, index) => {
|
|
673
673
|
const entryPath = `${path}.attributes[${index}]`;
|
|
@@ -680,53 +680,53 @@ function parsePrimitive(value, path, budget) {
|
|
|
680
680
|
ensureUnique(attributes.map(({ semantic }) => semantic), `${path}.attributes`, "duplicate attribute semantic");
|
|
681
681
|
if (!attributes.some(({ semantic }) => semantic === "POSITION")) return fail("invalid-value", `${path}.attributes`, "primitive requires POSITION");
|
|
682
682
|
const output = {
|
|
683
|
-
id: readId(requireKey(
|
|
684
|
-
topology: readEnum(requireKey(
|
|
683
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
684
|
+
topology: readEnum(requireKey(record4, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
|
|
685
685
|
attributes
|
|
686
686
|
};
|
|
687
|
-
assignOptional(output, "indicesAccessorId", readOptional(
|
|
688
|
-
assignOptional(output, "materialId", readOptional(
|
|
689
|
-
assignOptional(output, "metadata", readOptional(
|
|
687
|
+
assignOptional(output, "indicesAccessorId", readOptional(record4, "indicesAccessorId", readId, path));
|
|
688
|
+
assignOptional(output, "materialId", readOptional(record4, "materialId", readId, path));
|
|
689
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
690
690
|
return output;
|
|
691
691
|
}
|
|
692
692
|
function parseMesh(value, path, budget) {
|
|
693
|
-
const
|
|
694
|
-
const input = readArray(requireKey(
|
|
693
|
+
const record4 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
|
|
694
|
+
const input = readArray(requireKey(record4, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
695
695
|
consumeBudget(budget, "primitives", input.length, GPU_MODEL_DOCUMENT_LIMITS.primitives, path, "primitive");
|
|
696
696
|
const output = {
|
|
697
|
-
id: readId(requireKey(
|
|
697
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
698
698
|
primitives: Object.freeze(input.map((entry, index) => parsePrimitive(entry, `${path}.primitives[${index}]`, budget)))
|
|
699
699
|
};
|
|
700
|
-
assignOptional(output, "name", readOptional(
|
|
701
|
-
assignOptional(output, "metadata", readOptional(
|
|
700
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
701
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
702
702
|
return output;
|
|
703
703
|
}
|
|
704
704
|
function parseTextureTransform(value, path) {
|
|
705
|
-
const
|
|
706
|
-
if (Reflect.ownKeys(
|
|
705
|
+
const record4 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
|
|
706
|
+
if (Reflect.ownKeys(record4).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
|
|
707
707
|
const output = {};
|
|
708
|
-
assignOptional(output, "offset", readOptional(
|
|
709
|
-
assignOptional(output, "rotationRadians", readOptional(
|
|
710
|
-
assignOptional(output, "scale", readOptional(
|
|
708
|
+
assignOptional(output, "offset", readOptional(record4, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
709
|
+
assignOptional(output, "rotationRadians", readOptional(record4, "rotationRadians", readFinite, path));
|
|
710
|
+
assignOptional(output, "scale", readOptional(record4, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
711
711
|
return output;
|
|
712
712
|
}
|
|
713
713
|
function parseTextureBinding(value, path) {
|
|
714
|
-
const
|
|
714
|
+
const record4 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
|
|
715
715
|
const output = {
|
|
716
|
-
textureId: readId(requireKey(
|
|
717
|
-
colorSpace: readEnum(requireKey(
|
|
718
|
-
intendedUsage: readEnum(requireKey(
|
|
716
|
+
textureId: readId(requireKey(record4, "textureId", path), `${path}.textureId`),
|
|
717
|
+
colorSpace: readEnum(requireKey(record4, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
718
|
+
intendedUsage: readEnum(requireKey(record4, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
|
|
719
719
|
};
|
|
720
|
-
assignOptional(output, "samplerId", readOptional(
|
|
721
|
-
assignOptional(output, "texCoordSet", readOptional(
|
|
722
|
-
assignOptional(output, "transform", readOptional(
|
|
720
|
+
assignOptional(output, "samplerId", readOptional(record4, "samplerId", readId, path));
|
|
721
|
+
assignOptional(output, "texCoordSet", readOptional(record4, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
|
|
722
|
+
assignOptional(output, "transform", readOptional(record4, "transform", parseTextureTransform, path));
|
|
723
723
|
return output;
|
|
724
724
|
}
|
|
725
725
|
function parseMaterialTextures(value, path) {
|
|
726
726
|
const slots = Object.keys(MATERIAL_TEXTURE_SLOTS);
|
|
727
|
-
const
|
|
727
|
+
const record4 = readRecord(value, path, slots);
|
|
728
728
|
const output = {};
|
|
729
|
-
for (const slot of slots) assignOptional(output, slot, readOptional(
|
|
729
|
+
for (const slot of slots) assignOptional(output, slot, readOptional(record4, slot, parseTextureBinding, path));
|
|
730
730
|
return output;
|
|
731
731
|
}
|
|
732
732
|
function parseMaterial(value, path, budget) {
|
|
@@ -752,138 +752,138 @@ function parseMaterial(value, path, budget) {
|
|
|
752
752
|
"extensions",
|
|
753
753
|
"sourceMetadata"
|
|
754
754
|
];
|
|
755
|
-
const
|
|
756
|
-
const alphaMode = readEnum(requireKey(
|
|
757
|
-
const alphaCutoff = readOptional(
|
|
755
|
+
const record4 = readRecord(value, path, keys);
|
|
756
|
+
const alphaMode = readEnum(requireKey(record4, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
|
|
757
|
+
const alphaCutoff = readOptional(record4, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
|
|
758
758
|
if (alphaMode === "mask" && alphaCutoff === void 0) return fail("invalid-value", `${path}.alphaCutoff`, "mask materials require alphaCutoff");
|
|
759
759
|
if (alphaMode !== "mask" && alphaCutoff !== void 0) return fail("invalid-value", `${path}.alphaCutoff`, "alphaCutoff is only valid for mask materials");
|
|
760
760
|
const output = {
|
|
761
|
-
id: readId(requireKey(
|
|
762
|
-
workflow: readEnum(requireKey(
|
|
761
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
762
|
+
workflow: readEnum(requireKey(record4, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
|
|
763
763
|
alphaMode,
|
|
764
|
-
baseColorFactor: readNumberTuple(requireKey(
|
|
765
|
-
textures: parseMaterialTextures(requireKey(
|
|
766
|
-
extensions: readMetadata(requireKey(
|
|
767
|
-
sourceMetadata: readMetadata(requireKey(
|
|
764
|
+
baseColorFactor: readNumberTuple(requireKey(record4, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
|
|
765
|
+
textures: parseMaterialTextures(requireKey(record4, "textures", path), `${path}.textures`),
|
|
766
|
+
extensions: readMetadata(requireKey(record4, "extensions", path), `${path}.extensions`, budget),
|
|
767
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
768
768
|
};
|
|
769
|
-
assignOptional(output, "name", readOptional(
|
|
769
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
770
770
|
assignOptional(output, "alphaCutoff", alphaCutoff);
|
|
771
|
-
assignOptional(output, "doubleSided", readOptional(
|
|
771
|
+
assignOptional(output, "doubleSided", readOptional(record4, "doubleSided", readBoolean, path));
|
|
772
772
|
for (const key of ["metallicFactor", "roughnessFactor", "transmissionFactor", "clearcoatFactor", "clearcoatRoughnessFactor", "sheenRoughnessFactor", "occlusionStrength"]) {
|
|
773
|
-
assignOptional(output, key, readOptional(
|
|
773
|
+
assignOptional(output, key, readOptional(record4, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
774
774
|
}
|
|
775
|
-
assignOptional(output, "normalScale", readOptional(
|
|
776
|
-
assignOptional(output, "emissiveFactor", readOptional(
|
|
777
|
-
assignOptional(output, "sheenColorFactor", readOptional(
|
|
775
|
+
assignOptional(output, "normalScale", readOptional(record4, "normalScale", readFinite, path));
|
|
776
|
+
assignOptional(output, "emissiveFactor", readOptional(record4, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
|
|
777
|
+
assignOptional(output, "sheenColorFactor", readOptional(record4, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
|
|
778
778
|
return output;
|
|
779
779
|
}
|
|
780
780
|
function parseTextureSampler(value, path) {
|
|
781
|
-
const
|
|
782
|
-
const output = { id: readId(requireKey(
|
|
783
|
-
assignOptional(output, "wrapS", readOptional(
|
|
784
|
-
assignOptional(output, "wrapT", readOptional(
|
|
785
|
-
assignOptional(output, "minFilter", readOptional(
|
|
786
|
-
assignOptional(output, "magFilter", readOptional(
|
|
787
|
-
assignOptional(output, "mipFilter", readOptional(
|
|
781
|
+
const record4 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
|
|
782
|
+
const output = { id: readId(requireKey(record4, "id", path), `${path}.id`) };
|
|
783
|
+
assignOptional(output, "wrapS", readOptional(record4, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
784
|
+
assignOptional(output, "wrapT", readOptional(record4, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
785
|
+
assignOptional(output, "minFilter", readOptional(record4, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
786
|
+
assignOptional(output, "magFilter", readOptional(record4, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
787
|
+
assignOptional(output, "mipFilter", readOptional(record4, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
|
|
788
788
|
return output;
|
|
789
789
|
}
|
|
790
790
|
function parseTexture(value, path, budget) {
|
|
791
|
-
const
|
|
792
|
-
const usages = Object.freeze(readArray(requireKey(
|
|
791
|
+
const record4 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
|
|
792
|
+
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)));
|
|
793
793
|
ensureUnique(usages, `${path}.intendedUsage`, "duplicate intended texture usage");
|
|
794
794
|
const output = {
|
|
795
|
-
id: readId(requireKey(
|
|
796
|
-
contentHash: readSha256(requireKey(
|
|
797
|
-
imageResourceId: readId(requireKey(
|
|
795
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
796
|
+
contentHash: readSha256(requireKey(record4, "contentHash", path), `${path}.contentHash`),
|
|
797
|
+
imageResourceId: readId(requireKey(record4, "imageResourceId", path), `${path}.imageResourceId`),
|
|
798
798
|
intendedUsage: usages,
|
|
799
|
-
colorSpace: readEnum(requireKey(
|
|
800
|
-
exportEmbeddingPolicy: readEnum(requireKey(
|
|
801
|
-
sourceMetadata: readMetadata(requireKey(
|
|
799
|
+
colorSpace: readEnum(requireKey(record4, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
800
|
+
exportEmbeddingPolicy: readEnum(requireKey(record4, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
|
|
801
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
802
802
|
};
|
|
803
|
-
assignOptional(output, "fileName", readOptional(
|
|
804
|
-
assignOptional(output, "relativePath", readOptional(
|
|
805
|
-
assignOptional(output, "mimeType", readOptional(
|
|
806
|
-
assignOptional(output, "byteLength", readOptional(
|
|
807
|
-
assignOptional(output, "sampler", readOptional(
|
|
803
|
+
assignOptional(output, "fileName", readOptional(record4, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
|
|
804
|
+
assignOptional(output, "relativePath", readOptional(record4, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
|
|
805
|
+
assignOptional(output, "mimeType", readOptional(record4, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
|
|
806
|
+
assignOptional(output, "byteLength", readOptional(record4, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
807
|
+
assignOptional(output, "sampler", readOptional(record4, "sampler", parseTextureSampler, path));
|
|
808
808
|
return output;
|
|
809
809
|
}
|
|
810
810
|
function parseSkeleton(value, path, budget) {
|
|
811
|
-
const
|
|
812
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
813
|
-
const rootJointIds = Object.freeze(readArray(requireKey(
|
|
811
|
+
const record4 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
|
|
812
|
+
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}]`)));
|
|
813
|
+
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}]`)));
|
|
814
814
|
consumeBudget(budget, "skinJoints", jointIds.length + rootJointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "rig joint");
|
|
815
815
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skeleton joint");
|
|
816
816
|
ensureUnique(rootJointIds, `${path}.rootJointIds`, "duplicate root joint");
|
|
817
817
|
const output = {
|
|
818
|
-
id: readId(requireKey(
|
|
818
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
819
819
|
jointIds,
|
|
820
820
|
rootJointIds,
|
|
821
|
-
sourceMetadata: readMetadata(requireKey(
|
|
821
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
822
822
|
};
|
|
823
|
-
assignOptional(output, "name", readOptional(
|
|
823
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
824
824
|
return output;
|
|
825
825
|
}
|
|
826
826
|
function parseJoint(value, path, budget) {
|
|
827
|
-
const
|
|
827
|
+
const record4 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
|
|
828
828
|
const output = {
|
|
829
|
-
id: readId(requireKey(
|
|
830
|
-
nodeId: readId(requireKey(
|
|
831
|
-
restLocalTransform: parseTransformTrs(requireKey(
|
|
832
|
-
sourceMetadata: readMetadata(requireKey(
|
|
829
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
830
|
+
nodeId: readId(requireKey(record4, "nodeId", path), `${path}.nodeId`),
|
|
831
|
+
restLocalTransform: parseTransformTrs(requireKey(record4, "restLocalTransform", path), `${path}.restLocalTransform`),
|
|
832
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
833
833
|
};
|
|
834
|
-
assignOptional(output, "name", readOptional(
|
|
835
|
-
assignOptional(output, "parentJointId", readOptional(
|
|
836
|
-
assignOptional(output, "inverseBindMatrix", readOptional(
|
|
834
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
835
|
+
assignOptional(output, "parentJointId", readOptional(record4, "parentJointId", readId, path));
|
|
836
|
+
assignOptional(output, "inverseBindMatrix", readOptional(record4, "inverseBindMatrix", parseMatrix, path));
|
|
837
837
|
return output;
|
|
838
838
|
}
|
|
839
|
-
function parseWeightRows(value, path, rows, columns,
|
|
839
|
+
function parseWeightRows(value, path, rows, columns, integer2, budget) {
|
|
840
840
|
const input = readArray(value, path, rows, rows);
|
|
841
841
|
consumeBudget(budget, "weightVertices", input.length, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, path, "weight vertex");
|
|
842
842
|
consumeBudget(budget, "weightInfluences", input.length * columns, GPU_MODEL_DOCUMENT_LIMITS.weightInfluences, path, "weight influence");
|
|
843
843
|
return Object.freeze(input.map((row, rowIndex) => {
|
|
844
|
-
const parsed = readNumberTuple(row, `${path}[${rowIndex}]`, columns, 0,
|
|
845
|
-
if (
|
|
844
|
+
const parsed = readNumberTuple(row, `${path}[${rowIndex}]`, columns, 0, integer2 ? Number.MAX_SAFE_INTEGER : 1);
|
|
845
|
+
if (integer2 && parsed.some((entry) => !Number.isSafeInteger(entry))) return fail("invalid-value", `${path}[${rowIndex}]`, "joint indices must be safe integers");
|
|
846
846
|
return parsed;
|
|
847
847
|
}));
|
|
848
848
|
}
|
|
849
849
|
function parseVertexWeightSet(value, path, budget) {
|
|
850
|
-
const
|
|
851
|
-
const influences = readSafeInteger(requireKey(
|
|
850
|
+
const record4 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
|
|
851
|
+
const influences = readSafeInteger(requireKey(record4, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
|
|
852
852
|
if (influences % 4 !== 0) return fail("invalid-value", `${path}.influencesPerVertex`, "influencesPerVertex must be a multiple of four");
|
|
853
|
-
const jointRows = readArray(requireKey(
|
|
854
|
-
const weightRows = readArray(requireKey(
|
|
853
|
+
const jointRows = readArray(requireKey(record4, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
854
|
+
const weightRows = readArray(requireKey(record4, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
855
855
|
if (jointRows.length !== weightRows.length) return fail("invalid-value", path, "joint and weight row counts must match");
|
|
856
856
|
return {
|
|
857
|
-
primitiveId: readId(requireKey(
|
|
857
|
+
primitiveId: readId(requireKey(record4, "primitiveId", path), `${path}.primitiveId`),
|
|
858
858
|
influencesPerVertex: influences,
|
|
859
859
|
joints: parseWeightRows(jointRows, `${path}.joints`, jointRows.length, influences, true, budget),
|
|
860
860
|
weights: parseWeightRows(weightRows, `${path}.weights`, weightRows.length, influences, false, budget)
|
|
861
861
|
};
|
|
862
862
|
}
|
|
863
863
|
function parseSkin(value, path, budget) {
|
|
864
|
-
const
|
|
865
|
-
const jointIds = Object.freeze(readArray(requireKey(
|
|
864
|
+
const record4 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
|
|
865
|
+
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}]`)));
|
|
866
866
|
consumeBudget(budget, "skinJoints", jointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "skin joint");
|
|
867
867
|
ensureUnique(jointIds, `${path}.jointIds`, "duplicate skin joint");
|
|
868
|
-
const inverseInput = readArray(requireKey(
|
|
868
|
+
const inverseInput = readArray(requireKey(record4, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
|
|
869
869
|
if (inverseInput.length !== jointIds.length) return fail("invalid-value", `${path}.inverseBindMatrices`, "one inverse bind matrix is required per skin joint");
|
|
870
|
-
const weightInput = readArray(requireKey(
|
|
870
|
+
const weightInput = readArray(requireKey(record4, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
871
871
|
const weightSets = Object.freeze(weightInput.map((entry, index) => parseVertexWeightSet(entry, `${path}.weightSets[${index}]`, budget)));
|
|
872
872
|
ensureUnique(weightSets.map(({ primitiveId }) => primitiveId), `${path}.weightSets`, "duplicate primitive weight set");
|
|
873
873
|
return {
|
|
874
|
-
id: readId(requireKey(
|
|
875
|
-
skeletonId: readId(requireKey(
|
|
874
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
875
|
+
skeletonId: readId(requireKey(record4, "skeletonId", path), `${path}.skeletonId`),
|
|
876
876
|
jointIds,
|
|
877
877
|
inverseBindMatrices: Object.freeze(inverseInput.map((entry, index) => parseMatrix(entry, `${path}.inverseBindMatrices[${index}]`))),
|
|
878
878
|
weightSets,
|
|
879
|
-
sourceMetadata: readMetadata(requireKey(
|
|
879
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
880
880
|
};
|
|
881
881
|
}
|
|
882
882
|
function parseBlendShapeDelta(value, path) {
|
|
883
|
-
const
|
|
884
|
-
const output = { primitiveId: readId(requireKey(
|
|
883
|
+
const record4 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
|
|
884
|
+
const output = { primitiveId: readId(requireKey(record4, "primitiveId", path), `${path}.primitiveId`) };
|
|
885
885
|
for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
|
|
886
|
-
assignOptional(output, key, readOptional(
|
|
886
|
+
assignOptional(output, key, readOptional(record4, key, readId, path));
|
|
887
887
|
}
|
|
888
888
|
if (!output.positionAccessorId && !output.normalAccessorId && !output.tangentAccessorId) {
|
|
889
889
|
return fail("invalid-value", path, "blend-shape delta requires at least one accessor");
|
|
@@ -891,8 +891,8 @@ function parseBlendShapeDelta(value, path) {
|
|
|
891
891
|
return output;
|
|
892
892
|
}
|
|
893
893
|
function parseBlendShape(value, path, budget) {
|
|
894
|
-
const
|
|
895
|
-
const deltaInput = readArray(requireKey(
|
|
894
|
+
const record4 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
|
|
895
|
+
const deltaInput = readArray(requireKey(record4, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
|
|
896
896
|
consumeBudget(
|
|
897
897
|
budget,
|
|
898
898
|
"blendShapeDeltas",
|
|
@@ -904,96 +904,96 @@ function parseBlendShape(value, path, budget) {
|
|
|
904
904
|
const deltas = Object.freeze(deltaInput.map((entry, index) => parseBlendShapeDelta(entry, `${path}.deltas[${index}]`)));
|
|
905
905
|
ensureUnique(deltas.map(({ primitiveId }) => primitiveId), `${path}.deltas`, "duplicate blend-shape primitive delta");
|
|
906
906
|
const output = {
|
|
907
|
-
id: readId(requireKey(
|
|
908
|
-
meshId: readId(requireKey(
|
|
907
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
908
|
+
meshId: readId(requireKey(record4, "meshId", path), `${path}.meshId`),
|
|
909
909
|
deltas,
|
|
910
|
-
sourceMetadata: readMetadata(requireKey(
|
|
910
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
911
911
|
};
|
|
912
|
-
assignOptional(output, "name", readOptional(
|
|
913
|
-
assignOptional(output, "defaultWeight", readOptional(
|
|
912
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
913
|
+
assignOptional(output, "defaultWeight", readOptional(record4, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
|
|
914
914
|
return output;
|
|
915
915
|
}
|
|
916
916
|
function parseAnimationSampler(value, path, budget) {
|
|
917
|
-
const
|
|
918
|
-
const timesInput = readArray(requireKey(
|
|
919
|
-
const valuesInput = readArray(requireKey(
|
|
917
|
+
const record4 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
|
|
918
|
+
const timesInput = readArray(requireKey(record4, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
919
|
+
const valuesInput = readArray(requireKey(record4, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
920
920
|
consumeBudget(budget, "animationValues", timesInput.length + valuesInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationValues, path, "animation value");
|
|
921
921
|
const times = Object.freeze(timesInput.map((entry, index) => readFinite(entry, `${path}.inputTimesSeconds[${index}]`, 0)));
|
|
922
922
|
for (let index = 1; index < times.length; index += 1) {
|
|
923
923
|
if (times[index] <= times[index - 1]) return fail("invalid-value", `${path}.inputTimesSeconds`, "animation input times must be strictly increasing");
|
|
924
924
|
}
|
|
925
925
|
return {
|
|
926
|
-
id: readId(requireKey(
|
|
927
|
-
inputAccessorId: readId(requireKey(
|
|
928
|
-
outputAccessorId: readId(requireKey(
|
|
926
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
927
|
+
inputAccessorId: readId(requireKey(record4, "inputAccessorId", path), `${path}.inputAccessorId`),
|
|
928
|
+
outputAccessorId: readId(requireKey(record4, "outputAccessorId", path), `${path}.outputAccessorId`),
|
|
929
929
|
inputTimesSeconds: times,
|
|
930
930
|
outputValues: Object.freeze(valuesInput.map((entry, index) => readFinite(entry, `${path}.outputValues[${index}]`))),
|
|
931
|
-
interpolation: readEnum(requireKey(
|
|
932
|
-
sourceMetadata: readMetadata(requireKey(
|
|
931
|
+
interpolation: readEnum(requireKey(record4, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
|
|
932
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
933
933
|
};
|
|
934
934
|
}
|
|
935
935
|
function parseAnimationChannel(value, path, budget) {
|
|
936
|
-
const
|
|
936
|
+
const record4 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
|
|
937
937
|
return {
|
|
938
|
-
id: readId(requireKey(
|
|
939
|
-
samplerId: readId(requireKey(
|
|
940
|
-
targetType: readEnum(requireKey(
|
|
941
|
-
targetId: readId(requireKey(
|
|
942
|
-
sourceMetadata: readMetadata(requireKey(
|
|
938
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
939
|
+
samplerId: readId(requireKey(record4, "samplerId", path), `${path}.samplerId`),
|
|
940
|
+
targetType: readEnum(requireKey(record4, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
|
|
941
|
+
targetId: readId(requireKey(record4, "targetId", path), `${path}.targetId`),
|
|
942
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
943
943
|
};
|
|
944
944
|
}
|
|
945
945
|
function parseAnimation(value, path, budget) {
|
|
946
|
-
const
|
|
947
|
-
const samplerInput = readArray(requireKey(
|
|
948
|
-
const channelInput = readArray(requireKey(
|
|
946
|
+
const record4 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
|
|
947
|
+
const samplerInput = readArray(requireKey(record4, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
|
|
948
|
+
const channelInput = readArray(requireKey(record4, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
|
|
949
949
|
consumeBudget(budget, "animationSamplers", samplerInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, path, "animation sampler");
|
|
950
950
|
consumeBudget(budget, "animationChannels", channelInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, path, "animation channel");
|
|
951
951
|
const output = {
|
|
952
|
-
id: readId(requireKey(
|
|
953
|
-
durationSeconds: readFinite(requireKey(
|
|
952
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
953
|
+
durationSeconds: readFinite(requireKey(record4, "durationSeconds", path), `${path}.durationSeconds`, 0),
|
|
954
954
|
samplers: Object.freeze(samplerInput.map((entry, index) => parseAnimationSampler(entry, `${path}.samplers[${index}]`, budget))),
|
|
955
955
|
channels: Object.freeze(channelInput.map((entry, index) => parseAnimationChannel(entry, `${path}.channels[${index}]`, budget))),
|
|
956
|
-
sourceMetadata: readMetadata(requireKey(
|
|
956
|
+
sourceMetadata: readMetadata(requireKey(record4, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
957
957
|
};
|
|
958
|
-
assignOptional(output, "name", readOptional(
|
|
958
|
+
assignOptional(output, "name", readOptional(record4, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
959
959
|
return output;
|
|
960
960
|
}
|
|
961
961
|
function parseAnalyticGeometry(value, path, budget) {
|
|
962
|
-
const
|
|
963
|
-
if (requireKey(
|
|
962
|
+
const record4 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
|
|
963
|
+
if (requireKey(record4, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
|
|
964
964
|
const output = {
|
|
965
|
-
id: readId(requireKey(
|
|
965
|
+
id: readId(requireKey(record4, "id", path), `${path}.id`),
|
|
966
966
|
kind: "brep-placeholder",
|
|
967
|
-
sourceResourceId: readId(requireKey(
|
|
968
|
-
sourceUnit: readEnum(requireKey(
|
|
967
|
+
sourceResourceId: readId(requireKey(record4, "sourceResourceId", path), `${path}.sourceResourceId`),
|
|
968
|
+
sourceUnit: readEnum(requireKey(record4, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
|
|
969
969
|
};
|
|
970
|
-
assignOptional(output, "tessellatedMeshId", readOptional(
|
|
971
|
-
assignOptional(output, "metadata", readOptional(
|
|
970
|
+
assignOptional(output, "tessellatedMeshId", readOptional(record4, "tessellatedMeshId", readId, path));
|
|
971
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
972
972
|
return output;
|
|
973
973
|
}
|
|
974
974
|
function parseProvenance(value, budget) {
|
|
975
975
|
const path = "$.provenance";
|
|
976
|
-
const
|
|
976
|
+
const record4 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
|
|
977
977
|
const output = {
|
|
978
|
-
sourceFormat: readString(requireKey(
|
|
979
|
-
sourceContentHash: readSha256(requireKey(
|
|
980
|
-
converterId: readString(requireKey(
|
|
981
|
-
converterVersion: readString(requireKey(
|
|
978
|
+
sourceFormat: readString(requireKey(record4, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
|
|
979
|
+
sourceContentHash: readSha256(requireKey(record4, "sourceContentHash", path), `${path}.sourceContentHash`),
|
|
980
|
+
converterId: readString(requireKey(record4, "converterId", path), `${path}.converterId`, 128, TOKEN),
|
|
981
|
+
converterVersion: readString(requireKey(record4, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
|
|
982
982
|
};
|
|
983
|
-
assignOptional(output, "provider", readOptional(
|
|
984
|
-
assignOptional(output, "sourceIdentifier", readOptional(
|
|
985
|
-
assignOptional(output, "metadata", readOptional(
|
|
983
|
+
assignOptional(output, "provider", readOptional(record4, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
984
|
+
assignOptional(output, "sourceIdentifier", readOptional(record4, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
|
|
985
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
986
986
|
return output;
|
|
987
987
|
}
|
|
988
988
|
function parseDiagnostic(value, path, budget) {
|
|
989
|
-
const
|
|
989
|
+
const record4 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
|
|
990
990
|
const output = {
|
|
991
|
-
severity: readEnum(requireKey(
|
|
992
|
-
code: readString(requireKey(
|
|
993
|
-
message: readString(requireKey(
|
|
991
|
+
severity: readEnum(requireKey(record4, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
|
|
992
|
+
code: readString(requireKey(record4, "code", path), `${path}.code`, 128, TOKEN),
|
|
993
|
+
message: readString(requireKey(record4, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
|
|
994
994
|
};
|
|
995
|
-
assignOptional(output, "path", readOptional(
|
|
996
|
-
assignOptional(output, "metadata", readOptional(
|
|
995
|
+
assignOptional(output, "path", readOptional(record4, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
|
|
996
|
+
assignOptional(output, "metadata", readOptional(record4, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
|
|
997
997
|
return output;
|
|
998
998
|
}
|
|
999
999
|
function ensureUnique(values, path, detail = "duplicate identity") {
|
|
@@ -1634,11 +1634,11 @@ var DOCUMENT_KEYS = Object.freeze([
|
|
|
1634
1634
|
]);
|
|
1635
1635
|
function parseGpuModelDocument(value) {
|
|
1636
1636
|
const path = "$";
|
|
1637
|
-
const
|
|
1638
|
-
const schemaVersion = requireKey(
|
|
1637
|
+
const record4 = readRecord(value, path, DOCUMENT_KEYS);
|
|
1638
|
+
const schemaVersion = requireKey(record4, "schemaVersion", path);
|
|
1639
1639
|
if (schemaVersion !== GPU_MODEL_DOCUMENT_SCHEMA_VERSION) return fail("invalid-value", "$.schemaVersion", "unsupported schema version");
|
|
1640
1640
|
const parseCollection = (key, maximum, parser, minimum = 0) => {
|
|
1641
|
-
const input = readArray(requireKey(
|
|
1641
|
+
const input = readArray(requireKey(record4, key, path), `$.${key}`, maximum, minimum);
|
|
1642
1642
|
return Object.freeze(input.map((entry, index) => parser(entry, `$.${key}[${index}]`)));
|
|
1643
1643
|
};
|
|
1644
1644
|
const budget = {
|
|
@@ -1659,10 +1659,10 @@ function parseGpuModelDocument(value) {
|
|
|
1659
1659
|
geometryWorldAccessorInstances: 0,
|
|
1660
1660
|
geometryWorldVertices: 0
|
|
1661
1661
|
};
|
|
1662
|
-
const roots = Object.freeze(readArray(requireKey(
|
|
1662
|
+
const roots = Object.freeze(readArray(requireKey(record4, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
|
|
1663
1663
|
const document = {
|
|
1664
1664
|
schemaVersion,
|
|
1665
|
-
coordinateSystem: parseCoordinateSystem(requireKey(
|
|
1665
|
+
coordinateSystem: parseCoordinateSystem(requireKey(record4, "coordinateSystem", path)),
|
|
1666
1666
|
roots,
|
|
1667
1667
|
nodes: parseCollection("nodes", GPU_MODEL_DOCUMENT_LIMITS.nodes, (entry, entryPath) => parseNode(entry, entryPath, budget), 1),
|
|
1668
1668
|
resources: parseCollection("resources", GPU_MODEL_DOCUMENT_LIMITS.resources, parseVerifiedResource, 1),
|
|
@@ -1676,10 +1676,10 @@ function parseGpuModelDocument(value) {
|
|
|
1676
1676
|
blendShapes: parseCollection("blendShapes", GPU_MODEL_DOCUMENT_LIMITS.blendShapes, (entry, entryPath) => parseBlendShape(entry, entryPath, budget)),
|
|
1677
1677
|
animations: parseCollection("animations", GPU_MODEL_DOCUMENT_LIMITS.animations, (entry, entryPath) => parseAnimation(entry, entryPath, budget)),
|
|
1678
1678
|
analyticGeometry: parseCollection("analyticGeometry", GPU_MODEL_DOCUMENT_LIMITS.analyticGeometry, (entry, entryPath) => parseAnalyticGeometry(entry, entryPath, budget)),
|
|
1679
|
-
bounds: parseBounds(requireKey(
|
|
1680
|
-
provenance: parseProvenance(requireKey(
|
|
1679
|
+
bounds: parseBounds(requireKey(record4, "bounds", path), "$.bounds"),
|
|
1680
|
+
provenance: parseProvenance(requireKey(record4, "provenance", path), budget),
|
|
1681
1681
|
diagnostics: parseCollection("diagnostics", GPU_MODEL_DOCUMENT_LIMITS.diagnostics, (entry, entryPath) => parseDiagnostic(entry, entryPath, budget)),
|
|
1682
|
-
metadata: readMetadata(requireKey(
|
|
1682
|
+
metadata: readMetadata(requireKey(record4, "metadata", path), "$.metadata", budget)
|
|
1683
1683
|
};
|
|
1684
1684
|
if (document.meshes.length === 0) return fail("invalid-value", "$.meshes", "v1 document bounds require at least one canonical mesh");
|
|
1685
1685
|
validateReferences(document, budget);
|
|
@@ -1716,8 +1716,8 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1716
1716
|
value.resources.forEach((resource, index) => assertVerifiedResourceEvidence(resource, limits, `$.resources[${index}]`));
|
|
1717
1717
|
return value;
|
|
1718
1718
|
}
|
|
1719
|
-
const
|
|
1720
|
-
const resourceInput = readArray(requireKey(
|
|
1719
|
+
const record4 = readRecord(value, "$", DOCUMENT_KEYS);
|
|
1720
|
+
const resourceInput = readArray(requireKey(record4, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
|
|
1721
1721
|
const parsed = resourceInput.map((entry, index) => {
|
|
1722
1722
|
if (typeof entry === "object" && entry !== null && verifiedResources.has(entry) && verifiedResourceBytes.has(entry)) return entry;
|
|
1723
1723
|
return parseResourceHeader(entry, `$.resources[${index}]`);
|
|
@@ -1730,7 +1730,7 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
|
|
|
1730
1730
|
}
|
|
1731
1731
|
const candidate = {};
|
|
1732
1732
|
for (const key of DOCUMENT_KEYS) {
|
|
1733
|
-
if (Object.hasOwn(
|
|
1733
|
+
if (Object.hasOwn(record4, key)) candidate[key] = record4[key];
|
|
1734
1734
|
}
|
|
1735
1735
|
candidate.resources = verified;
|
|
1736
1736
|
return createGpuModelDocument(candidate);
|
|
@@ -1795,11 +1795,11 @@ async function hashGpuModelDocumentCanonical(document) {
|
|
|
1795
1795
|
return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("");
|
|
1796
1796
|
}
|
|
1797
1797
|
function resolveStaticDemoLimits(options) {
|
|
1798
|
-
const
|
|
1799
|
-
const enabled = readBoolean(requireKey(
|
|
1798
|
+
const record4 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
|
|
1799
|
+
const enabled = readBoolean(requireKey(record4, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
|
|
1800
1800
|
if (!enabled) return fail("feature-disabled", "$.options.pvoxModelsEnabled", `${GPU_MODEL_STATIC_DEMO_FEATURE_FLAG} is disabled`);
|
|
1801
|
-
if (!Object.hasOwn(
|
|
1802
|
-
const overrides = readRecord(
|
|
1801
|
+
if (!Object.hasOwn(record4, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
|
|
1802
|
+
const overrides = readRecord(record4.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
|
|
1803
1803
|
const limits = { ...GPU_MODEL_STATIC_DEMO_LIMITS };
|
|
1804
1804
|
for (const [key, ceiling] of Object.entries(GPU_MODEL_STATIC_DEMO_LIMITS)) {
|
|
1805
1805
|
if (Object.hasOwn(overrides, key)) limits[key] = readSafeInteger(overrides[key], `$.options.limits.${key}`, 1, ceiling);
|
|
@@ -2168,15 +2168,15 @@ function snapshot(value, depth, budget, ancestors, memberLimit = GPU_MODEL_DIAGN
|
|
|
2168
2168
|
return value;
|
|
2169
2169
|
}
|
|
2170
2170
|
if (typeof value !== "object" || ancestors.has(value)) fail2();
|
|
2171
|
-
const
|
|
2171
|
+
const array2 = Array.isArray(value);
|
|
2172
2172
|
const prototype = Object.getPrototypeOf(value);
|
|
2173
|
-
if (
|
|
2173
|
+
if (array2 ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
|
|
2174
2174
|
const keys = Reflect.ownKeys(value);
|
|
2175
2175
|
const maxMembers = memberLimit;
|
|
2176
|
-
if (keys.length > maxMembers + (
|
|
2176
|
+
if (keys.length > maxMembers + (array2 ? 1 : 0)) fail2();
|
|
2177
2177
|
ancestors.add(value);
|
|
2178
2178
|
charge(budget, 2);
|
|
2179
|
-
if (
|
|
2179
|
+
if (array2) {
|
|
2180
2180
|
const length = Object.getOwnPropertyDescriptor(value, "length").value;
|
|
2181
2181
|
if (length > maxMembers || keys.length !== length + 1) fail2();
|
|
2182
2182
|
const result2 = [];
|
|
@@ -2261,7 +2261,7 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2261
2261
|
const diagnostics = [];
|
|
2262
2262
|
const repairs = [];
|
|
2263
2263
|
const losses = [];
|
|
2264
|
-
let
|
|
2264
|
+
let accepted2 = true;
|
|
2265
2265
|
for (const issue of issues) {
|
|
2266
2266
|
const base = { code: issue.code, path: issue.path, message: issue.message };
|
|
2267
2267
|
if (issue.kind === "violation") {
|
|
@@ -2272,20 +2272,565 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2272
2272
|
repairs.push(Object.freeze({ ruleId: rule.id, originalValue: issue.originalValue, action: rule.action, severity: rule.severity, affectedPath: issue.path, confidence: rule.confidence }));
|
|
2273
2273
|
diagnostics.push(Object.freeze({ ...base, kind: "repair", severity: rule.severity, ruleId: rule.id }));
|
|
2274
2274
|
} else {
|
|
2275
|
-
|
|
2275
|
+
accepted2 = false;
|
|
2276
2276
|
diagnostics.push(Object.freeze({ ...base, kind: "blocking-error", severity: "error", ruleId: issue.ruleId }));
|
|
2277
2277
|
}
|
|
2278
2278
|
} else if (issue.kind === "unsupported") {
|
|
2279
|
-
|
|
2279
|
+
accepted2 = false;
|
|
2280
2280
|
diagnostics.push(Object.freeze({ ...issue, severity: "error" }));
|
|
2281
2281
|
} else if (issue.kind === "loss") {
|
|
2282
|
-
if (!allowLoss)
|
|
2282
|
+
if (!allowLoss) accepted2 = false;
|
|
2283
2283
|
const loss = Object.freeze({ ...issue, severity: "warning" });
|
|
2284
2284
|
losses.push(loss);
|
|
2285
2285
|
diagnostics.push(loss);
|
|
2286
2286
|
} else diagnostics.push(Object.freeze({ ...issue, severity: "warning" }));
|
|
2287
2287
|
}
|
|
2288
|
-
return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2288
|
+
return Object.freeze({ mode, standard, accepted: accepted2, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// src/resource-graph.ts
|
|
2292
|
+
var GPU_MODEL_RESOURCE_GRAPH_LIMITS = Object.freeze({
|
|
2293
|
+
nodes: 65536,
|
|
2294
|
+
edges: 262144,
|
|
2295
|
+
packages: 256,
|
|
2296
|
+
pathLength: 1024
|
|
2297
|
+
});
|
|
2298
|
+
var GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION = "plasius.gpu-model-resource-graph/1";
|
|
2299
|
+
var KINDS = ["resource", "accessor", "texture", "material", "skeleton", "skin", "animation", "provenance", "package"];
|
|
2300
|
+
var GpuModelResourceGraphError = class extends TypeError {
|
|
2301
|
+
constructor(code) {
|
|
2302
|
+
super(`Invalid GPU model resource graph: ${code}`);
|
|
2303
|
+
this.code = code;
|
|
2304
|
+
this.name = "GpuModelResourceGraphError";
|
|
2305
|
+
}
|
|
2306
|
+
code;
|
|
2307
|
+
};
|
|
2308
|
+
function fail3(code = "invalid-value") {
|
|
2309
|
+
throw new GpuModelResourceGraphError(code);
|
|
2310
|
+
}
|
|
2311
|
+
function identifier(value) {
|
|
2312
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail3();
|
|
2313
|
+
return value;
|
|
2314
|
+
}
|
|
2315
|
+
function choice(value, choices) {
|
|
2316
|
+
if (typeof value !== "string" || !choices.includes(value)) fail3();
|
|
2317
|
+
return value;
|
|
2318
|
+
}
|
|
2319
|
+
function gpuModelResourceKey(kind, id) {
|
|
2320
|
+
return JSON.stringify([choice(kind, KINDS), identifier(id)]);
|
|
2321
|
+
}
|
|
2322
|
+
function record2(value, keys) {
|
|
2323
|
+
if (typeof value !== "object" || value === null || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
2324
|
+
const names = Reflect.ownKeys(value);
|
|
2325
|
+
if (names.length !== keys.length || names.some((key) => typeof key !== "string" || !keys.includes(key))) fail3();
|
|
2326
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2327
|
+
for (const key of keys) {
|
|
2328
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2329
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2330
|
+
result[key] = descriptor.value;
|
|
2331
|
+
}
|
|
2332
|
+
return result;
|
|
2333
|
+
}
|
|
2334
|
+
function array(value, maximum) {
|
|
2335
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail3();
|
|
2336
|
+
if (value.length > maximum) fail3("limit-exceeded");
|
|
2337
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
|
|
2338
|
+
const output = [];
|
|
2339
|
+
for (let index = 0; index < value.length; index++) {
|
|
2340
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
2341
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
|
|
2342
|
+
output.push(descriptor.value);
|
|
2343
|
+
}
|
|
2344
|
+
return output;
|
|
2345
|
+
}
|
|
2346
|
+
function relativePath(value) {
|
|
2347
|
+
if (typeof value !== "string" || value.length > GPU_MODEL_RESOURCE_GRAPH_LIMITS.pathLength || !/^[A-Za-z0-9._/-]+$/u.test(value)) fail3();
|
|
2348
|
+
for (const part of value.split("/")) {
|
|
2349
|
+
if (!part || part.length > 255 || part === "." || part === ".." || part.endsWith(".") || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part)) fail3();
|
|
2350
|
+
}
|
|
2351
|
+
return value;
|
|
2352
|
+
}
|
|
2353
|
+
function parseLocation(value) {
|
|
2354
|
+
if (typeof value !== "object" || value === null) fail3();
|
|
2355
|
+
const kind = Object.getOwnPropertyDescriptor(value, "kind");
|
|
2356
|
+
if (!kind || !("value" in kind)) fail3();
|
|
2357
|
+
if (kind.value === "embedded") {
|
|
2358
|
+
record2(value, ["kind"]);
|
|
2359
|
+
return Object.freeze({ kind: "embedded" });
|
|
2360
|
+
}
|
|
2361
|
+
const location = record2(value, ["kind", "relativePath"]);
|
|
2362
|
+
if (location.kind !== "external") fail3();
|
|
2363
|
+
return Object.freeze({ kind: "external", relativePath: relativePath(location.relativePath) });
|
|
2364
|
+
}
|
|
2365
|
+
var graphs = /* @__PURE__ */ new WeakSet();
|
|
2366
|
+
function isGpuModelResourceGraph(value) {
|
|
2367
|
+
return typeof value === "object" && value !== null && graphs.has(value);
|
|
2368
|
+
}
|
|
2369
|
+
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
2370
|
+
function createGpuModelResourceGraph(document, input) {
|
|
2371
|
+
try {
|
|
2372
|
+
return buildGraph(document, input);
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
if (error instanceof GpuModelResourceGraphError) throw error;
|
|
2375
|
+
fail3();
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
function buildGraph(document, input) {
|
|
2379
|
+
if (!isGpuModelDocument(document)) fail3("unverified-document");
|
|
2380
|
+
const raw = record2(input, ["resources", "packages"]);
|
|
2381
|
+
const resourceInputs = array(raw.resources, GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes);
|
|
2382
|
+
const packageInputs = array(raw.packages, GPU_MODEL_RESOURCE_GRAPH_LIMITS.packages);
|
|
2383
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2384
|
+
let edgeCount = 0;
|
|
2385
|
+
const add = (kind, id, value, dependencies) => {
|
|
2386
|
+
const key = gpuModelResourceKey(kind, id);
|
|
2387
|
+
if (nodes.has(key)) fail3("duplicate-reference");
|
|
2388
|
+
if (nodes.size >= GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes) fail3("limit-exceeded");
|
|
2389
|
+
edgeCount += dependencies.length;
|
|
2390
|
+
if (edgeCount > GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges) fail3("limit-exceeded");
|
|
2391
|
+
nodes.set(key, Object.freeze({ kind, id, key, value, dependencies: Object.freeze([...new Set(dependencies)].sort(compare)) }));
|
|
2392
|
+
};
|
|
2393
|
+
const resources = new Map(document.resources.map((resource) => [resource.id, resource]));
|
|
2394
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2395
|
+
for (const entry of resourceInputs) {
|
|
2396
|
+
const binding = record2(entry, ["resourceId", "location", "exportEmbeddingPolicy"]);
|
|
2397
|
+
const id = identifier(binding.resourceId);
|
|
2398
|
+
const resource = resources.get(id);
|
|
2399
|
+
if (!resource) fail3("missing-reference");
|
|
2400
|
+
const location = parseLocation(binding.location);
|
|
2401
|
+
const exportEmbeddingPolicy = choice(binding.exportEmbeddingPolicy, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]);
|
|
2402
|
+
if (location.kind === "embedded" && exportEmbeddingPolicy === "forbid-embedding") fail3();
|
|
2403
|
+
if (location.kind === "external") {
|
|
2404
|
+
const path = location.relativePath.toLowerCase();
|
|
2405
|
+
if (paths.has(path)) fail3("duplicate-reference");
|
|
2406
|
+
paths.add(path);
|
|
2407
|
+
}
|
|
2408
|
+
add("resource", id, Object.freeze({ resource, location, exportEmbeddingPolicy }), []);
|
|
2409
|
+
}
|
|
2410
|
+
for (const path of paths) {
|
|
2411
|
+
for (let index = path.indexOf("/"); index !== -1; index = path.indexOf("/", index + 1)) {
|
|
2412
|
+
if (paths.has(path.slice(0, index))) fail3("duplicate-reference");
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
if (nodes.size !== resources.size) fail3("missing-reference");
|
|
2416
|
+
addDocumentNodes(document, add);
|
|
2417
|
+
for (const entry of packageInputs) {
|
|
2418
|
+
const pkg = record2(entry, ["id", "members"]);
|
|
2419
|
+
const id = identifier(pkg.id);
|
|
2420
|
+
const members = [];
|
|
2421
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2422
|
+
for (const entry2 of array(pkg.members, GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges - edgeCount)) {
|
|
2423
|
+
const reference = record2(entry2, ["kind", "id"]);
|
|
2424
|
+
const member = Object.freeze({ kind: choice(reference.kind, KINDS), id: identifier(reference.id) });
|
|
2425
|
+
const key = gpuModelResourceKey(member.kind, member.id);
|
|
2426
|
+
if (dependencies.has(key)) fail3("duplicate-reference");
|
|
2427
|
+
dependencies.add(key);
|
|
2428
|
+
members.push(member);
|
|
2429
|
+
}
|
|
2430
|
+
members.sort((a, b) => compare(gpuModelResourceKey(a.kind, a.id), gpuModelResourceKey(b.kind, b.id)));
|
|
2431
|
+
add("package", id, Object.freeze({ id, members: Object.freeze(members) }), [...dependencies]);
|
|
2432
|
+
}
|
|
2433
|
+
validateDag(nodes);
|
|
2434
|
+
const result = Object.freeze({ schemaVersion: GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION, nodes: Object.freeze([...nodes.values()].sort((a, b) => compare(a.key, b.key))) });
|
|
2435
|
+
graphs.add(result);
|
|
2436
|
+
return result;
|
|
2437
|
+
}
|
|
2438
|
+
function addDocumentNodes(document, add) {
|
|
2439
|
+
const key = gpuModelResourceKey;
|
|
2440
|
+
for (const value of document.accessors) add("accessor", value.id, value, [key("resource", value.resourceId)]);
|
|
2441
|
+
for (const value of document.textures) add("texture", value.id, value, [key("resource", value.imageResourceId)]);
|
|
2442
|
+
for (const value of document.materials) add("material", value.id, value, Object.values(value.textures).map((binding) => key("texture", binding.textureId)));
|
|
2443
|
+
const joints = new Map(document.joints.map((joint) => [joint.id, joint]));
|
|
2444
|
+
for (const skeleton of document.skeletons) add("skeleton", skeleton.id, Object.freeze({ skeleton, joints: Object.freeze(skeleton.jointIds.map((id) => joints.get(id))) }), []);
|
|
2445
|
+
const primitives = new Map(document.meshes.flatMap((mesh) => mesh.primitives.map((primitive) => [primitive.id, primitive])));
|
|
2446
|
+
for (const value of document.skins) {
|
|
2447
|
+
const dependencies = [key("skeleton", value.skeletonId)];
|
|
2448
|
+
for (const weightSet of value.weightSets) {
|
|
2449
|
+
for (const attribute of primitives.get(weightSet.primitiveId).attributes) {
|
|
2450
|
+
if (/^(JOINTS|WEIGHTS)_/u.test(attribute.semantic)) dependencies.push(key("accessor", attribute.accessorId));
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
add("skin", value.id, value, dependencies);
|
|
2454
|
+
}
|
|
2455
|
+
for (const value of document.animations) add("animation", value.id, value, value.samplers.flatMap((sampler) => [key("accessor", sampler.inputAccessorId), key("accessor", sampler.outputAccessorId)]));
|
|
2456
|
+
add("provenance", "source", document.provenance, []);
|
|
2457
|
+
}
|
|
2458
|
+
function validateDag(nodes) {
|
|
2459
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
2460
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
2461
|
+
const queue = [];
|
|
2462
|
+
for (const node of nodes.values()) {
|
|
2463
|
+
remaining.set(node.key, node.dependencies.length);
|
|
2464
|
+
if (!node.dependencies.length) queue.push(node.key);
|
|
2465
|
+
for (const dependency of node.dependencies) {
|
|
2466
|
+
if (!nodes.has(dependency)) fail3("missing-reference");
|
|
2467
|
+
const users = dependents.get(dependency) ?? [];
|
|
2468
|
+
users.push(node.key);
|
|
2469
|
+
dependents.set(dependency, users);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
for (let index = 0; index < queue.length; index++) {
|
|
2473
|
+
for (const dependent of dependents.get(queue[index]) ?? []) {
|
|
2474
|
+
const count = remaining.get(dependent) - 1;
|
|
2475
|
+
remaining.set(dependent, count);
|
|
2476
|
+
if (!count) queue.push(dependent);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (queue.length !== nodes.size) fail3("graph-cycle");
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// src/adapter-registry.ts
|
|
2483
|
+
var MODEL_REGISTRY_LIMITS = Object.freeze({ adapters: 128, concurrent: 8, timeoutMs: 3e4, maxTimeoutMs: 3e5, string: 4096, mapEntries: 64, manifestEntries: 4096, memoryBytes: 64 * 1024 * 1024 });
|
|
2484
|
+
var ModelRegistryError = class extends TypeError {
|
|
2485
|
+
constructor(code) {
|
|
2486
|
+
super(`GPU model registry: ${code}`);
|
|
2487
|
+
this.code = code;
|
|
2488
|
+
this.name = "ModelRegistryError";
|
|
2489
|
+
}
|
|
2490
|
+
code;
|
|
2491
|
+
};
|
|
2492
|
+
function fail4(code = "invalid-input") {
|
|
2493
|
+
throw new ModelRegistryError(code);
|
|
2494
|
+
}
|
|
2495
|
+
var sourceKinds = ["file-path", "url", "blob-storage-url", "blob", "array-buffer", "uint8-array", "stream"];
|
|
2496
|
+
var targetKinds = ["file-path", "blob-storage-url", "blob", "array-buffer", "uint8-array", "stream", "package"];
|
|
2497
|
+
var bases = /* @__PURE__ */ new WeakMap();
|
|
2498
|
+
function createModelResultBase(input) {
|
|
2499
|
+
const report = evaluateGpuModelDiagnostics(input);
|
|
2500
|
+
const value = Object.freeze({ report, diagnostics: report.diagnostics, warnings: Object.freeze(report.diagnostics.filter((d) => d.severity === "warning")), repairs: report.repairs, lossReports: report.losses });
|
|
2501
|
+
bases.set(report, value);
|
|
2502
|
+
return value;
|
|
2503
|
+
}
|
|
2504
|
+
function record3(value, allowed) {
|
|
2505
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
2506
|
+
const keys = Reflect.ownKeys(value);
|
|
2507
|
+
if (keys.length > MODEL_REGISTRY_LIMITS.mapEntries) fail4();
|
|
2508
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
2509
|
+
for (const key of keys) {
|
|
2510
|
+
if (typeof key !== "string" || allowed && !allowed.includes(key)) fail4();
|
|
2511
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2512
|
+
if (!("value" in descriptor)) fail4();
|
|
2513
|
+
output[key] = descriptor.value;
|
|
2514
|
+
}
|
|
2515
|
+
return output;
|
|
2516
|
+
}
|
|
2517
|
+
function text2(value) {
|
|
2518
|
+
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();
|
|
2519
|
+
return value;
|
|
2520
|
+
}
|
|
2521
|
+
function format(value) {
|
|
2522
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(value)) fail4();
|
|
2523
|
+
return value;
|
|
2524
|
+
}
|
|
2525
|
+
function enumeration(value, values) {
|
|
2526
|
+
if (!values.includes(value)) fail4();
|
|
2527
|
+
return value;
|
|
2528
|
+
}
|
|
2529
|
+
function boolean2(value) {
|
|
2530
|
+
if (typeof value !== "boolean") fail4();
|
|
2531
|
+
return value;
|
|
2532
|
+
}
|
|
2533
|
+
function integer(value, max, min = 0) {
|
|
2534
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) fail4();
|
|
2535
|
+
return value;
|
|
2536
|
+
}
|
|
2537
|
+
function list(value, max) {
|
|
2538
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length > max || Reflect.ownKeys(value).length !== value.length + 1) fail4();
|
|
2539
|
+
return Array.from({ length: value.length }, (_, i) => {
|
|
2540
|
+
const d = Object.getOwnPropertyDescriptor(value, String(i));
|
|
2541
|
+
if (!d || !("value" in d)) fail4();
|
|
2542
|
+
return d.value;
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
function strings(value) {
|
|
2546
|
+
const input = record3(value);
|
|
2547
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2548
|
+
for (const [key, entry] of Object.entries(input)) out[text2(key)] = text2(entry);
|
|
2549
|
+
return Object.freeze(out);
|
|
2550
|
+
}
|
|
2551
|
+
function metadata(value) {
|
|
2552
|
+
const v = record3(value, ["formatId", "displayName", "supportedSourceKinds", "supportedTargetKinds", "supportsAnimations", "supportsSkins", "supportsAnalyticGeometry"]);
|
|
2553
|
+
const kinds = (input, allowed) => {
|
|
2554
|
+
const values = list(input, allowed.length).map((k) => enumeration(k, allowed));
|
|
2555
|
+
if (new Set(values).size !== values.length) fail4();
|
|
2556
|
+
return Object.freeze(values);
|
|
2557
|
+
};
|
|
2558
|
+
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) });
|
|
2559
|
+
}
|
|
2560
|
+
function snapshotIO(input, source) {
|
|
2561
|
+
const v = record3(input);
|
|
2562
|
+
const kind = enumeration(v.kind, source ? sourceKinds : targetKinds);
|
|
2563
|
+
const common = source ? ["kind", "fileNameHint", "mimeTypeHint", "provenance"] : ["kind"];
|
|
2564
|
+
const specific = source ? {
|
|
2565
|
+
"file-path": ["path"],
|
|
2566
|
+
url: ["url", "headers"],
|
|
2567
|
+
"blob-storage-url": ["url", "credentialMode"],
|
|
2568
|
+
blob: ["blob"],
|
|
2569
|
+
"array-buffer": ["bytes"],
|
|
2570
|
+
"uint8-array": ["bytes"],
|
|
2571
|
+
stream: ["stream", "byteLengthHint"]
|
|
2572
|
+
} : {
|
|
2573
|
+
"file-path": ["path", "overwrite"],
|
|
2574
|
+
"blob-storage-url": ["url", "overwrite", "contentType", "metadata"],
|
|
2575
|
+
blob: ["mimeType", "fileName"],
|
|
2576
|
+
"array-buffer": [],
|
|
2577
|
+
"uint8-array": [],
|
|
2578
|
+
stream: ["stream", "contentType"],
|
|
2579
|
+
package: ["packageFormat", "destinationHint", "includeSourcePayload"]
|
|
2580
|
+
};
|
|
2581
|
+
if (Object.keys(v).some((k) => ![...common, ...specific[kind]].includes(k))) fail4();
|
|
2582
|
+
const out = { kind };
|
|
2583
|
+
for (const key of ["fileNameHint", "mimeTypeHint", "mimeType", "fileName", "contentType", "destinationHint"]) if (v[key] !== void 0) out[key] = text2(v[key]);
|
|
2584
|
+
for (const key of ["overwrite", "includeSourcePayload"]) if (v[key] !== void 0) out[key] = boolean2(v[key]);
|
|
2585
|
+
for (const key of ["headers", "metadata"]) if (v[key] !== void 0) out[key] = strings(v[key]);
|
|
2586
|
+
if (v.provenance !== void 0) out.provenance = strings(record3(v.provenance, ["sourceId", "sourceSystem", "acquiredAt", "licenseHint"]));
|
|
2587
|
+
if (v.credentialMode !== void 0) out.credentialMode = enumeration(v.credentialMode, ["anonymous", "signed", "runtime-resolved"]);
|
|
2588
|
+
if (v.byteLengthHint !== void 0) out.byteLengthHint = integer(v.byteLengthHint, Number.MAX_SAFE_INTEGER);
|
|
2589
|
+
if (kind === "file-path") out.path = text2(v.path);
|
|
2590
|
+
if (kind === "url" || kind === "blob-storage-url") {
|
|
2591
|
+
const url = text2(v.url);
|
|
2592
|
+
let parsed;
|
|
2593
|
+
try {
|
|
2594
|
+
parsed = new URL(url);
|
|
2595
|
+
} catch {
|
|
2596
|
+
return fail4();
|
|
2597
|
+
}
|
|
2598
|
+
if (!["https:", "http:"].includes(parsed.protocol) || parsed.username || parsed.password) fail4();
|
|
2599
|
+
out.url = url;
|
|
2600
|
+
}
|
|
2601
|
+
if (source && kind === "blob") {
|
|
2602
|
+
if (!(v.blob instanceof Blob)) fail4();
|
|
2603
|
+
integer(v.blob.size, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2604
|
+
out.blob = v.blob;
|
|
2605
|
+
}
|
|
2606
|
+
if (source && (kind === "array-buffer" || kind === "uint8-array")) {
|
|
2607
|
+
if (kind === "array-buffer") {
|
|
2608
|
+
if (!(v.bytes instanceof ArrayBuffer)) fail4();
|
|
2609
|
+
integer(v.bytes.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2610
|
+
out.bytes = v.bytes.slice(0);
|
|
2611
|
+
} else {
|
|
2612
|
+
if (!(v.bytes instanceof Uint8Array) || !(v.bytes.buffer instanceof ArrayBuffer)) fail4();
|
|
2613
|
+
integer(v.bytes.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2614
|
+
out.bytes = new Uint8Array(v.bytes);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
if (kind === "stream") {
|
|
2618
|
+
const stream = v.stream;
|
|
2619
|
+
if (!stream || typeof stream !== "object") fail4();
|
|
2620
|
+
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();
|
|
2621
|
+
out.stream = stream;
|
|
2622
|
+
}
|
|
2623
|
+
if (kind === "package") out.packageFormat = enumeration(v.packageFormat, ["zip", "tar", "directory-manifest"]);
|
|
2624
|
+
return Object.freeze(out);
|
|
2625
|
+
}
|
|
2626
|
+
function manifest(input) {
|
|
2627
|
+
const v = record3(input, ["format", "entries"]);
|
|
2628
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2629
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2630
|
+
const entries = list(v.entries, MODEL_REGISTRY_LIMITS.manifestEntries).map((entry) => {
|
|
2631
|
+
const e = record3(entry, ["id", "role", "path", "contentType"]);
|
|
2632
|
+
const id = text2(e.id);
|
|
2633
|
+
const path = text2(e.path);
|
|
2634
|
+
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();
|
|
2635
|
+
ids.add(id);
|
|
2636
|
+
paths.add(path.toLowerCase());
|
|
2637
|
+
return Object.freeze({ id, path, role: enumeration(e.role, ["mesh", "material", "texture", "animation", "auxiliary"]), ...e.contentType !== void 0 ? { contentType: text2(e.contentType) } : {} });
|
|
2638
|
+
});
|
|
2639
|
+
return Object.freeze({ format: enumeration(v.format, ["zip", "tar", "directory-manifest", "inline"]), entries: Object.freeze(entries) });
|
|
2640
|
+
}
|
|
2641
|
+
function reportBase(value, context) {
|
|
2642
|
+
const base = bases.get(value.report);
|
|
2643
|
+
if (!base || base.report.mode !== context.options.mode || ["diagnostics", "warnings", "repairs", "lossReports"].some((k) => value[k] !== base[k])) fail4("invalid-result");
|
|
2644
|
+
return base;
|
|
2645
|
+
}
|
|
2646
|
+
function accepted(report, context) {
|
|
2647
|
+
return report.accepted && (context.options.allowLoss || report.losses.length === 0);
|
|
2648
|
+
}
|
|
2649
|
+
function ledger(stages, ok, payload = {}) {
|
|
2650
|
+
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)) });
|
|
2651
|
+
}
|
|
2652
|
+
function createModelConversionRegistry(adapters, limits = {}) {
|
|
2653
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2654
|
+
const maxConcurrent = integer(record3(limits, ["maxConcurrent"]).maxConcurrent ?? MODEL_REGISTRY_LIMITS.concurrent, MODEL_REGISTRY_LIMITS.concurrent, 1);
|
|
2655
|
+
for (const input of list(adapters, MODEL_REGISTRY_LIMITS.adapters)) {
|
|
2656
|
+
let a;
|
|
2657
|
+
try {
|
|
2658
|
+
const v = record3(input, ["metadata", "inspect", "load", "export", "validate"]);
|
|
2659
|
+
const m = metadata(v.metadata);
|
|
2660
|
+
for (const name of ["inspect", "load", "export", "validate"]) if (typeof v[name] !== "function") fail4();
|
|
2661
|
+
const raw = input;
|
|
2662
|
+
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) });
|
|
2663
|
+
} catch {
|
|
2664
|
+
return fail4("invalid-adapter");
|
|
2665
|
+
}
|
|
2666
|
+
if (registry.has(a.metadata.formatId)) fail4("duplicate-format");
|
|
2667
|
+
registry.set(a.metadata.formatId, a);
|
|
2668
|
+
}
|
|
2669
|
+
let active = 0;
|
|
2670
|
+
function lookup(id) {
|
|
2671
|
+
const a = registry.get(format(id));
|
|
2672
|
+
if (!a) fail4("unsupported-format");
|
|
2673
|
+
return a;
|
|
2674
|
+
}
|
|
2675
|
+
function sourceFor(a, input) {
|
|
2676
|
+
const source = snapshotIO(input, true);
|
|
2677
|
+
if (!a.metadata.supportedSourceKinds.includes(source.kind)) fail4("unsupported-source");
|
|
2678
|
+
return source;
|
|
2679
|
+
}
|
|
2680
|
+
function targetFor(a, input) {
|
|
2681
|
+
const target = snapshotIO(input, false);
|
|
2682
|
+
if (!a.metadata.supportedTargetKinds.includes(target.kind)) fail4("unsupported-target");
|
|
2683
|
+
return target;
|
|
2684
|
+
}
|
|
2685
|
+
function check(context) {
|
|
2686
|
+
if (context.options.signal.aborted) fail4("aborted");
|
|
2687
|
+
if (Date.now() >= context.deadline) fail4("deadline-exceeded");
|
|
2688
|
+
}
|
|
2689
|
+
async function run(options, work) {
|
|
2690
|
+
const v = record3(options ?? {}, ["mode", "allowLoss", "signal", "timeoutMs"]);
|
|
2691
|
+
const mode = enumeration(v.mode ?? "strict", ["strict", "tolerant", "forensic"]);
|
|
2692
|
+
const allowLoss = v.allowLoss === void 0 ? false : boolean2(v.allowLoss);
|
|
2693
|
+
const timeoutMs = integer(v.timeoutMs ?? MODEL_REGISTRY_LIMITS.timeoutMs, MODEL_REGISTRY_LIMITS.maxTimeoutMs, 1);
|
|
2694
|
+
if (v.signal !== void 0 && !(v.signal instanceof AbortSignal)) fail4();
|
|
2695
|
+
const parent = v.signal;
|
|
2696
|
+
if (parent?.aborted) fail4("aborted");
|
|
2697
|
+
if (active >= maxConcurrent) fail4("capacity-exceeded");
|
|
2698
|
+
const controller = new AbortController();
|
|
2699
|
+
const context = { options: Object.freeze({ mode, allowLoss, timeoutMs, signal: controller.signal }), deadline: Date.now() + timeoutMs };
|
|
2700
|
+
active++;
|
|
2701
|
+
let timer;
|
|
2702
|
+
let abort;
|
|
2703
|
+
const pending = Promise.resolve().then(() => {
|
|
2704
|
+
check(context);
|
|
2705
|
+
return work(context);
|
|
2706
|
+
}).finally(() => {
|
|
2707
|
+
active--;
|
|
2708
|
+
});
|
|
2709
|
+
const stopped = new Promise((_, reject) => {
|
|
2710
|
+
abort = () => {
|
|
2711
|
+
controller.abort();
|
|
2712
|
+
reject(new ModelRegistryError("aborted"));
|
|
2713
|
+
};
|
|
2714
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
2715
|
+
timer = setTimeout(() => {
|
|
2716
|
+
controller.abort();
|
|
2717
|
+
reject(new ModelRegistryError("deadline-exceeded"));
|
|
2718
|
+
}, timeoutMs);
|
|
2719
|
+
});
|
|
2720
|
+
try {
|
|
2721
|
+
return await Promise.race([pending, stopped]);
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
if (error instanceof ModelRegistryError) throw error;
|
|
2724
|
+
return fail4();
|
|
2725
|
+
} finally {
|
|
2726
|
+
clearTimeout(timer);
|
|
2727
|
+
if (abort) parent?.removeEventListener("abort", abort);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
async function invoke(a, operation, context, call, stages) {
|
|
2731
|
+
check(context);
|
|
2732
|
+
let raw;
|
|
2733
|
+
try {
|
|
2734
|
+
raw = await call();
|
|
2735
|
+
} catch {
|
|
2736
|
+
return fail4("adapter-failed");
|
|
2737
|
+
}
|
|
2738
|
+
check(context);
|
|
2739
|
+
try {
|
|
2740
|
+
const v = record3(raw, ["report", "diagnostics", "warnings", "repairs", "lossReports", ...{ inspect: ["detectedFormat", "capabilities"], load: ["document", "resourcePackage"], validate: ["valid"], export: ["target", "resourcePackage", "output"] }[operation]]);
|
|
2741
|
+
const base = reportBase(v, context);
|
|
2742
|
+
const result = { ...v, ...base };
|
|
2743
|
+
if (v.resourcePackage !== void 0) result.resourcePackage = manifest(v.resourcePackage);
|
|
2744
|
+
stages.push(Object.freeze({ formatId: a.metadata.formatId, operation, report: base.report }));
|
|
2745
|
+
return result;
|
|
2746
|
+
} catch {
|
|
2747
|
+
return fail4("invalid-result");
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
async function load(a, source, c, stages) {
|
|
2751
|
+
const v = await invoke(a, "load", c, () => a.load(source, c.options), stages);
|
|
2752
|
+
if (v.document !== void 0 && !isGpuModelDocument(v.document)) fail4("invalid-result");
|
|
2753
|
+
if (accepted(v.report, c) && !isGpuModelDocument(v.document)) fail4("invalid-result");
|
|
2754
|
+
return Object.freeze(v);
|
|
2755
|
+
}
|
|
2756
|
+
async function validate(a, input, c, stages) {
|
|
2757
|
+
const v = await invoke(a, "validate", c, () => a.validate(input, c.options), stages);
|
|
2758
|
+
if (typeof v.valid !== "boolean") fail4("invalid-result");
|
|
2759
|
+
return v.valid && accepted(v.report, c);
|
|
2760
|
+
}
|
|
2761
|
+
async function exportDocument(a, document, target, c, stages) {
|
|
2762
|
+
const issues = [];
|
|
2763
|
+
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"]]) {
|
|
2764
|
+
if (!supported && present) issues.push({ kind: "loss", code: "TARGET_CAPABILITY_LOSS", path, message: "Target cannot preserve canonical semantics", effect: "dropped" });
|
|
2765
|
+
}
|
|
2766
|
+
const report = evaluateGpuModelDiagnostics({ standard: "gltf-glb", mode: c.options.mode, allowLoss: c.options.allowLoss, issues, ...c.options.mode === "forensic" ? { rawSource: null } : {} });
|
|
2767
|
+
stages.push(Object.freeze({ formatId: a.metadata.formatId, operation: "capabilities", report }));
|
|
2768
|
+
if (!accepted(report, c) || !await validate(a, document, c, stages)) return ledger(stages, false, { document });
|
|
2769
|
+
const v = await invoke(a, "export", c, () => a.export(document, target, c.options), stages);
|
|
2770
|
+
if (v.target !== target.kind) fail4("invalid-result");
|
|
2771
|
+
try {
|
|
2772
|
+
if (v.output !== void 0) {
|
|
2773
|
+
const output = v.output;
|
|
2774
|
+
if (target.kind === "blob" && output instanceof Blob) {
|
|
2775
|
+
integer(output.size, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2776
|
+
} else if (target.kind === "array-buffer" && output instanceof ArrayBuffer) {
|
|
2777
|
+
integer(output.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2778
|
+
v.output = output.slice(0);
|
|
2779
|
+
} else if (target.kind === "uint8-array" && output instanceof Uint8Array && output.buffer instanceof ArrayBuffer) {
|
|
2780
|
+
integer(output.byteLength, MODEL_REGISTRY_LIMITS.memoryBytes);
|
|
2781
|
+
v.output = new Uint8Array(output);
|
|
2782
|
+
} else fail4();
|
|
2783
|
+
}
|
|
2784
|
+
} catch {
|
|
2785
|
+
return fail4("invalid-result");
|
|
2786
|
+
}
|
|
2787
|
+
const exported = Object.freeze(v);
|
|
2788
|
+
return ledger(stages, accepted(exported.report, c), { document, exported, ...exported.resourcePackage ? { resourcePackage: exported.resourcePackage } : {} });
|
|
2789
|
+
}
|
|
2790
|
+
const api = {
|
|
2791
|
+
capabilities: Object.freeze([...registry.values()].map((a) => a.metadata)),
|
|
2792
|
+
inspect: (id, input, options) => run(options, async (c) => {
|
|
2793
|
+
const a = lookup(id);
|
|
2794
|
+
const source = sourceFor(a, input);
|
|
2795
|
+
const stages = [];
|
|
2796
|
+
const v = await invoke(a, "inspect", c, () => a.inspect(source, c.options), stages);
|
|
2797
|
+
try {
|
|
2798
|
+
if (v.detectedFormat !== void 0 && format(v.detectedFormat) !== a.metadata.formatId) fail4();
|
|
2799
|
+
if (JSON.stringify(metadata(v.capabilities)) !== JSON.stringify(a.metadata)) fail4();
|
|
2800
|
+
} catch {
|
|
2801
|
+
return fail4("invalid-result");
|
|
2802
|
+
}
|
|
2803
|
+
return ledger(stages, accepted(v.report, c), { inspection: Object.freeze({ ...v, capabilities: a.metadata }) });
|
|
2804
|
+
}),
|
|
2805
|
+
load: (id, input, options) => run(options, async (c) => {
|
|
2806
|
+
const a = lookup(id);
|
|
2807
|
+
const stages = [];
|
|
2808
|
+
const result = await load(a, sourceFor(a, input), c, stages);
|
|
2809
|
+
return ledger(stages, accepted(result.report, c), { ...result.document ? { document: result.document } : {}, ...result.resourcePackage ? { resourcePackage: result.resourcePackage } : {} });
|
|
2810
|
+
}),
|
|
2811
|
+
validate: (id, input, options) => run(options, async (c) => {
|
|
2812
|
+
const a = lookup(id);
|
|
2813
|
+
const stages = [];
|
|
2814
|
+
return ledger(stages, await validate(a, isGpuModelDocument(input) ? input : sourceFor(a, input), c, stages));
|
|
2815
|
+
}),
|
|
2816
|
+
export: (id, document, input, options) => run(options, async (c) => {
|
|
2817
|
+
const a = lookup(id);
|
|
2818
|
+
if (!isGpuModelDocument(document)) fail4();
|
|
2819
|
+
return exportDocument(a, document, targetFor(a, input), c, []);
|
|
2820
|
+
}),
|
|
2821
|
+
convert: (request, options) => run(options, async (c) => {
|
|
2822
|
+
const v = record3(request, ["sourceFormat", "targetFormat", "source", "target"]);
|
|
2823
|
+
const from = lookup(v.sourceFormat);
|
|
2824
|
+
const to = lookup(v.targetFormat);
|
|
2825
|
+
const source = sourceFor(from, v.source);
|
|
2826
|
+
const target = targetFor(to, v.target);
|
|
2827
|
+
const stages = [];
|
|
2828
|
+
const result = await load(from, source, c, stages);
|
|
2829
|
+
if (!accepted(result.report, c)) return ledger(stages, false, { ...result.resourcePackage ? { resourcePackage: result.resourcePackage } : {} });
|
|
2830
|
+
return exportDocument(to, result.document, target, c, stages);
|
|
2831
|
+
})
|
|
2832
|
+
};
|
|
2833
|
+
return Object.freeze(api);
|
|
2289
2834
|
}
|
|
2290
2835
|
|
|
2291
2836
|
// src/index.ts
|
|
@@ -2301,6 +2846,8 @@ export {
|
|
|
2301
2846
|
GPU_MODEL_DOCUMENT_LIMITS,
|
|
2302
2847
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
2303
2848
|
GPU_MODEL_REPAIR_RULES,
|
|
2849
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
2850
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
2304
2851
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
2305
2852
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
2306
2853
|
GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -2311,15 +2858,23 @@ export {
|
|
|
2311
2858
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
2312
2859
|
GpuModelDiagnosticsError,
|
|
2313
2860
|
GpuModelDocumentError,
|
|
2861
|
+
GpuModelResourceGraphError,
|
|
2862
|
+
MODEL_REGISTRY_LIMITS,
|
|
2863
|
+
ModelRegistryError,
|
|
2314
2864
|
assertGpuModelStaticDemoNormalization,
|
|
2315
2865
|
canCreateGpuModelDocument,
|
|
2316
2866
|
createAndVerifyGpuModelDocument,
|
|
2317
2867
|
createGpuModelDocument,
|
|
2868
|
+
createGpuModelResourceGraph,
|
|
2318
2869
|
createGpuModelStaticDemoCompilerInput,
|
|
2870
|
+
createModelConversionRegistry,
|
|
2871
|
+
createModelResultBase,
|
|
2319
2872
|
encodeGpuModelDocumentCanonical,
|
|
2320
2873
|
evaluateGpuModelDiagnostics,
|
|
2874
|
+
gpuModelResourceKey,
|
|
2321
2875
|
hashGpuModelDocumentCanonical,
|
|
2322
2876
|
isGpuModelDocument,
|
|
2877
|
+
isGpuModelResourceGraph,
|
|
2323
2878
|
packageBootstrap,
|
|
2324
2879
|
packageName,
|
|
2325
2880
|
verifyGpuModelResource
|