@plasius/gpu-model-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/index.cjs +414 -211
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.js +408 -211
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.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(record3, key, path) {
|
|
176
|
+
if (!Object.hasOwn(record3, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
|
|
177
|
+
return record3[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(record3, key, parser, path) {
|
|
257
|
+
if (!Object.hasOwn(record3, key)) return void 0;
|
|
258
|
+
return parser(record3[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 record3 = value;
|
|
281
|
+
const prototype = Object.getPrototypeOf(record3);
|
|
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(record3);
|
|
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(record3, 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 record3 = 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(record3, 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 record3 = readRecord(value, path, ["min", "max"]);
|
|
318
|
+
const min = readNumberTuple(requireKey(record3, "min", path), `${path}.min`, 3);
|
|
319
|
+
const max = readNumberTuple(requireKey(record3, "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 record3 = readRecord(value, path, ["translation", "rotation", "scale"]);
|
|
327
|
+
const translation = readNumberTuple(requireKey(record3, "translation", path), `${path}.translation`, 3);
|
|
328
|
+
const rotation = readNumberTuple(requireKey(record3, "rotation", path), `${path}.rotation`, 4);
|
|
329
|
+
const scale = readNumberTuple(requireKey(record3, "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 record3 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
|
|
352
|
+
const kind = readEnum(requireKey(record3, "kind", path), `${path}.kind`, ["buffer", "image"]);
|
|
353
|
+
const mimeType = readString(requireKey(record3, "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(record3, "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(record3, "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(record3, "id", path), `${path}.id`),
|
|
368
368
|
kind,
|
|
369
|
-
contentHash: readSha256(requireKey(
|
|
369
|
+
contentHash: readSha256(requireKey(record3, "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 record3 = 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(record3, key)) continue;
|
|
382
|
+
const value = readSafeInteger(record3[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 record3 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
|
|
495
495
|
const output = {
|
|
496
|
-
valid: readBoolean(requireKey(
|
|
497
|
-
detectedMimeType: readString(requireKey(
|
|
496
|
+
valid: readBoolean(requireKey(record3, "valid", path), `${path}.valid`),
|
|
497
|
+
detectedMimeType: readString(requireKey(record3, "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(record3, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
500
|
+
assignOptional(output, "height", readOptional(record3, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
501
501
|
return output;
|
|
502
502
|
}
|
|
503
503
|
function bytesStartWith(bytes, signature) {
|
|
@@ -612,31 +612,31 @@ 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 record3 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
|
|
616
|
+
const children = Object.freeze(readArray(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
621
621
|
children,
|
|
622
|
-
localMatrix: parseMatrix(requireKey(
|
|
622
|
+
localMatrix: parseMatrix(requireKey(record3, "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(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
625
|
+
assignOptional(output, "meshId", readOptional(record3, "meshId", readId, path));
|
|
626
|
+
assignOptional(output, "skinId", readOptional(record3, "skinId", readId, path));
|
|
627
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
|
|
632
|
+
const componentType = readEnum(requireKey(record3, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
|
|
633
|
+
const elementType = readEnum(requireKey(record3, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
|
|
634
|
+
const normalized = readOptional(record3, "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(record3, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
|
|
639
|
+
const max = readOptional(record3, "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
642
|
const [componentMinimum, componentMaximum, integer] = COMPONENT_RANGES[componentType];
|
|
@@ -649,25 +649,25 @@ function parseAccessor(value, path, budget) {
|
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
|
-
const count = readSafeInteger(requireKey(
|
|
652
|
+
const count = readSafeInteger(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
656
|
+
resourceId: readId(requireKey(record3, "resourceId", path), `${path}.resourceId`),
|
|
657
|
+
byteOffset: readSafeInteger(requireKey(record3, "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(record3, "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 record3 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
|
|
670
|
+
const attributesInput = readArray(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
684
|
+
topology: readEnum(requireKey(record3, "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(record3, "indicesAccessorId", readId, path));
|
|
688
|
+
assignOptional(output, "materialId", readOptional(record3, "materialId", readId, path));
|
|
689
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
|
|
694
|
+
const input = readArray(requireKey(record3, "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(record3, "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(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
701
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
|
|
706
|
+
if (Reflect.ownKeys(record3).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(record3, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
709
|
+
assignOptional(output, "rotationRadians", readOptional(record3, "rotationRadians", readFinite, path));
|
|
710
|
+
assignOptional(output, "scale", readOptional(record3, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
|
|
711
711
|
return output;
|
|
712
712
|
}
|
|
713
713
|
function parseTextureBinding(value, path) {
|
|
714
|
-
const
|
|
714
|
+
const record3 = 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(record3, "textureId", path), `${path}.textureId`),
|
|
717
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
718
|
+
intendedUsage: readEnum(requireKey(record3, "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(record3, "samplerId", readId, path));
|
|
721
|
+
assignOptional(output, "texCoordSet", readOptional(record3, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
|
|
722
|
+
assignOptional(output, "transform", readOptional(record3, "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 record3 = 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(record3, slot, parseTextureBinding, path));
|
|
730
730
|
return output;
|
|
731
731
|
}
|
|
732
732
|
function parseMaterial(value, path, budget) {
|
|
@@ -752,88 +752,88 @@ 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 record3 = readRecord(value, path, keys);
|
|
756
|
+
const alphaMode = readEnum(requireKey(record3, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
|
|
757
|
+
const alphaCutoff = readOptional(record3, "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(record3, "id", path), `${path}.id`),
|
|
762
|
+
workflow: readEnum(requireKey(record3, "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(record3, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
|
|
765
|
+
textures: parseMaterialTextures(requireKey(record3, "textures", path), `${path}.textures`),
|
|
766
|
+
extensions: readMetadata(requireKey(record3, "extensions", path), `${path}.extensions`, budget),
|
|
767
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
768
768
|
};
|
|
769
|
-
assignOptional(output, "name", readOptional(
|
|
769
|
+
assignOptional(output, "name", readOptional(record3, "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(record3, "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(record3, 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(record3, "normalScale", readFinite, path));
|
|
776
|
+
assignOptional(output, "emissiveFactor", readOptional(record3, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
|
|
777
|
+
assignOptional(output, "sheenColorFactor", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
|
|
782
|
+
const output = { id: readId(requireKey(record3, "id", path), `${path}.id`) };
|
|
783
|
+
assignOptional(output, "wrapS", readOptional(record3, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
784
|
+
assignOptional(output, "wrapT", readOptional(record3, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
|
|
785
|
+
assignOptional(output, "minFilter", readOptional(record3, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
786
|
+
assignOptional(output, "magFilter", readOptional(record3, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
787
|
+
assignOptional(output, "mipFilter", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
|
|
792
|
+
const usages = Object.freeze(readArray(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
|
|
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(record3, "id", path), `${path}.id`),
|
|
796
|
+
contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
|
|
797
|
+
imageResourceId: readId(requireKey(record3, "imageResourceId", path), `${path}.imageResourceId`),
|
|
798
798
|
intendedUsage: usages,
|
|
799
|
-
colorSpace: readEnum(requireKey(
|
|
800
|
-
exportEmbeddingPolicy: readEnum(requireKey(
|
|
801
|
-
sourceMetadata: readMetadata(requireKey(
|
|
799
|
+
colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
|
|
800
|
+
exportEmbeddingPolicy: readEnum(requireKey(record3, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
|
|
801
|
+
sourceMetadata: readMetadata(requireKey(record3, "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(record3, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
|
|
804
|
+
assignOptional(output, "relativePath", readOptional(record3, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
|
|
805
|
+
assignOptional(output, "mimeType", readOptional(record3, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
|
|
806
|
+
assignOptional(output, "byteLength", readOptional(record3, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
|
|
807
|
+
assignOptional(output, "sampler", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
|
|
812
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
813
|
+
const rootJointIds = Object.freeze(readArray(requireKey(record3, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
|
|
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(record3, "id", path), `${path}.id`),
|
|
819
819
|
jointIds,
|
|
820
820
|
rootJointIds,
|
|
821
|
-
sourceMetadata: readMetadata(requireKey(
|
|
821
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
822
822
|
};
|
|
823
|
-
assignOptional(output, "name", readOptional(
|
|
823
|
+
assignOptional(output, "name", readOptional(record3, "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 record3 = 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(record3, "id", path), `${path}.id`),
|
|
830
|
+
nodeId: readId(requireKey(record3, "nodeId", path), `${path}.nodeId`),
|
|
831
|
+
restLocalTransform: parseTransformTrs(requireKey(record3, "restLocalTransform", path), `${path}.restLocalTransform`),
|
|
832
|
+
sourceMetadata: readMetadata(requireKey(record3, "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(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
835
|
+
assignOptional(output, "parentJointId", readOptional(record3, "parentJointId", readId, path));
|
|
836
|
+
assignOptional(output, "inverseBindMatrix", readOptional(record3, "inverseBindMatrix", parseMatrix, path));
|
|
837
837
|
return output;
|
|
838
838
|
}
|
|
839
839
|
function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
@@ -847,43 +847,43 @@ function parseWeightRows(value, path, rows, columns, integer, budget) {
|
|
|
847
847
|
}));
|
|
848
848
|
}
|
|
849
849
|
function parseVertexWeightSet(value, path, budget) {
|
|
850
|
-
const
|
|
851
|
-
const influences = readSafeInteger(requireKey(
|
|
850
|
+
const record3 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
|
|
851
|
+
const influences = readSafeInteger(requireKey(record3, "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(record3, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
|
|
854
|
+
const weightRows = readArray(requireKey(record3, "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(record3, "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 record3 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
|
|
865
|
+
const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
|
|
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(record3, "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(record3, "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(record3, "id", path), `${path}.id`),
|
|
875
|
+
skeletonId: readId(requireKey(record3, "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(record3, "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 record3 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
|
|
884
|
+
const output = { primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`) };
|
|
885
885
|
for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
|
|
886
|
-
assignOptional(output, key, readOptional(
|
|
886
|
+
assignOptional(output, key, readOptional(record3, 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 record3 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
|
|
895
|
+
const deltaInput = readArray(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
908
|
+
meshId: readId(requireKey(record3, "meshId", path), `${path}.meshId`),
|
|
909
909
|
deltas,
|
|
910
|
-
sourceMetadata: readMetadata(requireKey(
|
|
910
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
911
911
|
};
|
|
912
|
-
assignOptional(output, "name", readOptional(
|
|
913
|
-
assignOptional(output, "defaultWeight", readOptional(
|
|
912
|
+
assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
|
|
913
|
+
assignOptional(output, "defaultWeight", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
|
|
918
|
+
const timesInput = readArray(requireKey(record3, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
|
|
919
|
+
const valuesInput = readArray(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
927
|
+
inputAccessorId: readId(requireKey(record3, "inputAccessorId", path), `${path}.inputAccessorId`),
|
|
928
|
+
outputAccessorId: readId(requireKey(record3, "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(record3, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
|
|
932
|
+
sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
933
933
|
};
|
|
934
934
|
}
|
|
935
935
|
function parseAnimationChannel(value, path, budget) {
|
|
936
|
-
const
|
|
936
|
+
const record3 = 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(record3, "id", path), `${path}.id`),
|
|
939
|
+
samplerId: readId(requireKey(record3, "samplerId", path), `${path}.samplerId`),
|
|
940
|
+
targetType: readEnum(requireKey(record3, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
|
|
941
|
+
targetId: readId(requireKey(record3, "targetId", path), `${path}.targetId`),
|
|
942
|
+
sourceMetadata: readMetadata(requireKey(record3, "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 record3 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
|
|
947
|
+
const samplerInput = readArray(requireKey(record3, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
|
|
948
|
+
const channelInput = readArray(requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
953
|
+
durationSeconds: readFinite(requireKey(record3, "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(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
|
|
957
957
|
};
|
|
958
|
-
assignOptional(output, "name", readOptional(
|
|
958
|
+
assignOptional(output, "name", readOptional(record3, "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 record3 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
|
|
963
|
+
if (requireKey(record3, "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(record3, "id", path), `${path}.id`),
|
|
966
966
|
kind: "brep-placeholder",
|
|
967
|
-
sourceResourceId: readId(requireKey(
|
|
968
|
-
sourceUnit: readEnum(requireKey(
|
|
967
|
+
sourceResourceId: readId(requireKey(record3, "sourceResourceId", path), `${path}.sourceResourceId`),
|
|
968
|
+
sourceUnit: readEnum(requireKey(record3, "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(record3, "tessellatedMeshId", readId, path));
|
|
971
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = 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(record3, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
|
|
979
|
+
sourceContentHash: readSha256(requireKey(record3, "sourceContentHash", path), `${path}.sourceContentHash`),
|
|
980
|
+
converterId: readString(requireKey(record3, "converterId", path), `${path}.converterId`, 128, TOKEN),
|
|
981
|
+
converterVersion: readString(requireKey(record3, "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(record3, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
|
|
984
|
+
assignOptional(output, "sourceIdentifier", readOptional(record3, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
|
|
985
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = 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(record3, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
|
|
992
|
+
code: readString(requireKey(record3, "code", path), `${path}.code`, 128, TOKEN),
|
|
993
|
+
message: readString(requireKey(record3, "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(record3, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
|
|
996
|
+
assignOptional(output, "metadata", readOptional(record3, "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 record3 = readRecord(value, path, DOCUMENT_KEYS);
|
|
1638
|
+
const schemaVersion = requireKey(record3, "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(record3, 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(record3, "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(record3, "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(record3, "bounds", path), "$.bounds"),
|
|
1680
|
+
provenance: parseProvenance(requireKey(record3, "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(record3, "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 record3 = readRecord(value, "$", DOCUMENT_KEYS);
|
|
1720
|
+
const resourceInput = readArray(requireKey(record3, "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(record3, key)) candidate[key] = record3[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 record3 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
|
|
1799
|
+
const enabled = readBoolean(requireKey(record3, "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(record3, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
|
|
1802
|
+
const overrides = readRecord(record3.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 = [];
|
|
@@ -2288,6 +2288,197 @@ function evaluateGpuModelDiagnostics(value) {
|
|
|
2288
2288
|
return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
|
|
2289
2289
|
}
|
|
2290
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
|
+
|
|
2291
2482
|
// src/index.ts
|
|
2292
2483
|
var packageName = "@plasius/gpu-model-core";
|
|
2293
2484
|
var packageBootstrap = Object.freeze({
|
|
@@ -2301,6 +2492,8 @@ export {
|
|
|
2301
2492
|
GPU_MODEL_DOCUMENT_LIMITS,
|
|
2302
2493
|
GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
|
|
2303
2494
|
GPU_MODEL_REPAIR_RULES,
|
|
2495
|
+
GPU_MODEL_RESOURCE_GRAPH_LIMITS,
|
|
2496
|
+
GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
|
|
2304
2497
|
GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
|
|
2305
2498
|
GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
|
|
2306
2499
|
GPU_MODEL_STATIC_DEMO_LIMITS,
|
|
@@ -2311,15 +2504,19 @@ export {
|
|
|
2311
2504
|
GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
|
|
2312
2505
|
GpuModelDiagnosticsError,
|
|
2313
2506
|
GpuModelDocumentError,
|
|
2507
|
+
GpuModelResourceGraphError,
|
|
2314
2508
|
assertGpuModelStaticDemoNormalization,
|
|
2315
2509
|
canCreateGpuModelDocument,
|
|
2316
2510
|
createAndVerifyGpuModelDocument,
|
|
2317
2511
|
createGpuModelDocument,
|
|
2512
|
+
createGpuModelResourceGraph,
|
|
2318
2513
|
createGpuModelStaticDemoCompilerInput,
|
|
2319
2514
|
encodeGpuModelDocumentCanonical,
|
|
2320
2515
|
evaluateGpuModelDiagnostics,
|
|
2516
|
+
gpuModelResourceKey,
|
|
2321
2517
|
hashGpuModelDocumentCanonical,
|
|
2322
2518
|
isGpuModelDocument,
|
|
2519
|
+
isGpuModelResourceGraph,
|
|
2323
2520
|
packageBootstrap,
|
|
2324
2521
|
packageName,
|
|
2325
2522
|
verifyGpuModelResource
|