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