From 7238950f8e0372d33d7f7d2fd4fedbf1c133a164 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 16 Sep 2023 15:06:18 +0200 Subject: [PATCH] First draft of glTF extension validation --- ...ureIdAttributeFeatureCountInvalidType.gltf | 102 +++++++++++ ...reIdAttributeFeatureCountInvalidValue.gltf | 102 +++++++++++ ...FeatureIdAttributeFeatureCountMissing.gltf | 101 +++++++++++ .../meshFeatures/ValidFeatureIdAttribute.gltf | 102 +++++++++++ src/issues/GltfExtensionValidationIssues.ts | 23 +++ src/tileFormats/GltfValidator.ts | 8 + .../ExtMeshFeaturesValidator.ts | 162 ++++++++++++++++++ .../gltfExtensions/GltfExtensionValidators.ts | 67 ++++++++ 8 files changed, 667 insertions(+) create mode 100644 specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf create mode 100644 specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf create mode 100644 specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf create mode 100644 specs/data/gltfExtensions/meshFeatures/ValidFeatureIdAttribute.gltf create mode 100644 src/issues/GltfExtensionValidationIssues.ts create mode 100644 src/validation/gltfExtensions/ExtMeshFeaturesValidator.ts create mode 100644 src/validation/gltfExtensions/GltfExtensionValidators.ts diff --git a/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf new file mode 100644 index 00000000..934cfdde --- /dev/null +++ b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidType.gltf @@ -0,0 +1,102 @@ +{ + "extensionsUsed" : [ "EXT_mesh_features" ], + "accessors" : [ { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR", + "max" : [ 15 ], + "min" : [ 0 ] + }, { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 0.0, 0.0, 1.0 ], + "min" : [ 0.0, 0.0, 1.0 ] + }, { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5121, + "count" : 16, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + } ], + "asset" : { + "generator" : "JglTF from https://github.com/javagl/JglTF", + "version" : "2.0" + }, + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==", + "byteLength" : 496 + } ], + "bufferViews" : [ { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 240, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 432, + "byteLength" : 64, + "byteStride" : 4, + "target" : 34962 + } ], + "materials" : [ { + "pbrMetallicRoughness" : { + "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ], + "metallicFactor" : 0.0, + "roughnessFactor" : 1.0 + }, + "alphaMode" : "OPAQUE", + "doubleSided" : true + } ], + "meshes" : [ { + "primitives" : [ { + "extensions" : { + "EXT_mesh_features" : { + "featureIds" : [ { + "featureCount" : "NOT_AN_INTEGER", + "attribute" : 0 + } ] + } + }, + "attributes" : { + "POSITION" : 1, + "NORMAL" : 2, + "_FEATURE_ID_0" : 3 + }, + "indices" : 0, + "material" : 0, + "mode" : 4 + } ] + } ], + "nodes" : [ { + "mesh" : 0 + } ], + "scene" : 0, + "scenes" : [ { + "nodes" : [ 0 ] + } ] +} \ No newline at end of file diff --git a/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf new file mode 100644 index 00000000..420e3b16 --- /dev/null +++ b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountInvalidValue.gltf @@ -0,0 +1,102 @@ +{ + "extensionsUsed" : [ "EXT_mesh_features" ], + "accessors" : [ { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR", + "max" : [ 15 ], + "min" : [ 0 ] + }, { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 0.0, 0.0, 1.0 ], + "min" : [ 0.0, 0.0, 1.0 ] + }, { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5121, + "count" : 16, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + } ], + "asset" : { + "generator" : "JglTF from https://github.com/javagl/JglTF", + "version" : "2.0" + }, + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==", + "byteLength" : 496 + } ], + "bufferViews" : [ { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 240, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 432, + "byteLength" : 64, + "byteStride" : 4, + "target" : 34962 + } ], + "materials" : [ { + "pbrMetallicRoughness" : { + "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ], + "metallicFactor" : 0.0, + "roughnessFactor" : 1.0 + }, + "alphaMode" : "OPAQUE", + "doubleSided" : true + } ], + "meshes" : [ { + "primitives" : [ { + "extensions" : { + "EXT_mesh_features" : { + "featureIds" : [ { + "featureCount" : -12345, + "attribute" : 0 + } ] + } + }, + "attributes" : { + "POSITION" : 1, + "NORMAL" : 2, + "_FEATURE_ID_0" : 3 + }, + "indices" : 0, + "material" : 0, + "mode" : 4 + } ] + } ], + "nodes" : [ { + "mesh" : 0 + } ], + "scene" : 0, + "scenes" : [ { + "nodes" : [ 0 ] + } ] +} \ No newline at end of file diff --git a/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf new file mode 100644 index 00000000..4ae80ac2 --- /dev/null +++ b/specs/data/gltfExtensions/meshFeatures/FeatureIdAttributeFeatureCountMissing.gltf @@ -0,0 +1,101 @@ +{ + "extensionsUsed" : [ "EXT_mesh_features" ], + "accessors" : [ { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR", + "max" : [ 15 ], + "min" : [ 0 ] + }, { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 0.0, 0.0, 1.0 ], + "min" : [ 0.0, 0.0, 1.0 ] + }, { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5121, + "count" : 16, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + } ], + "asset" : { + "generator" : "JglTF from https://github.com/javagl/JglTF", + "version" : "2.0" + }, + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==", + "byteLength" : 496 + } ], + "bufferViews" : [ { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 240, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 432, + "byteLength" : 64, + "byteStride" : 4, + "target" : 34962 + } ], + "materials" : [ { + "pbrMetallicRoughness" : { + "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ], + "metallicFactor" : 0.0, + "roughnessFactor" : 1.0 + }, + "alphaMode" : "OPAQUE", + "doubleSided" : true + } ], + "meshes" : [ { + "primitives" : [ { + "extensions" : { + "EXT_mesh_features" : { + "featureIds" : [ { + "attribute" : 0 + } ] + } + }, + "attributes" : { + "POSITION" : 1, + "NORMAL" : 2, + "_FEATURE_ID_0" : 3 + }, + "indices" : 0, + "material" : 0, + "mode" : 4 + } ] + } ], + "nodes" : [ { + "mesh" : 0 + } ], + "scene" : 0, + "scenes" : [ { + "nodes" : [ 0 ] + } ] +} \ No newline at end of file diff --git a/specs/data/gltfExtensions/meshFeatures/ValidFeatureIdAttribute.gltf b/specs/data/gltfExtensions/meshFeatures/ValidFeatureIdAttribute.gltf new file mode 100644 index 00000000..6941256b --- /dev/null +++ b/specs/data/gltfExtensions/meshFeatures/ValidFeatureIdAttribute.gltf @@ -0,0 +1,102 @@ +{ + "extensionsUsed" : [ "EXT_mesh_features" ], + "accessors" : [ { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR", + "max" : [ 15 ], + "min" : [ 0 ] + }, { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 16, + "type" : "VEC3", + "max" : [ 0.0, 0.0, 1.0 ], + "min" : [ 0.0, 0.0, 1.0 ] + }, { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5121, + "count" : 16, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + } ], + "asset" : { + "generator" : "JglTF from https://github.com/javagl/JglTF", + "version" : "2.0" + }, + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAIAAQADAAIABAAFAAYABQAHAAYACAAJAAoACQALAAoADAANAA4ADQAPAA4AAAAAAAAAAAAAAAAAZmbmPgAAAAAAAAAAAAAAAGZm5j4AAAAAZmbmPmZm5j4AAAAAzcwMPwAAAAAAAAAAAACAPwAAAAAAAAAAzcwMP2Zm5j4AAAAAAACAP2Zm5j4AAAAAAAAAAM3MDD8AAAAAZmbmPs3MDD8AAAAAAAAAAAAAgD8AAAAAZmbmPgAAgD8AAAAAzcwMP83MDD8AAAAAAACAP83MDD8AAAAAzcwMPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAwAAAAMAAAADAAAAAwAAAA==", + "byteLength" : 496 + } ], + "bufferViews" : [ { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 240, + "byteLength" : 192, + "target" : 34962 + }, { + "buffer" : 0, + "byteOffset" : 432, + "byteLength" : 64, + "byteStride" : 4, + "target" : 34962 + } ], + "materials" : [ { + "pbrMetallicRoughness" : { + "baseColorFactor" : [ 0.5, 1.0, 0.5, 1.0 ], + "metallicFactor" : 0.0, + "roughnessFactor" : 1.0 + }, + "alphaMode" : "OPAQUE", + "doubleSided" : true + } ], + "meshes" : [ { + "primitives" : [ { + "extensions" : { + "EXT_mesh_features" : { + "featureIds" : [ { + "featureCount" : 4, + "attribute" : 0 + } ] + } + }, + "attributes" : { + "POSITION" : 1, + "NORMAL" : 2, + "_FEATURE_ID_0" : 3 + }, + "indices" : 0, + "material" : 0, + "mode" : 4 + } ] + } ], + "nodes" : [ { + "mesh" : 0 + } ], + "scene" : 0, + "scenes" : [ { + "nodes" : [ 0 ] + } ] +} \ No newline at end of file diff --git a/src/issues/GltfExtensionValidationIssues.ts b/src/issues/GltfExtensionValidationIssues.ts new file mode 100644 index 00000000..541741b5 --- /dev/null +++ b/src/issues/GltfExtensionValidationIssues.ts @@ -0,0 +1,23 @@ +import { ValidationIssue } from "../validation/ValidationIssue"; +import { ValidationIssueSeverity } from "../validation/ValidationIssueSeverity"; + +/** + * Methods to create `ValidationIssue` instances that describe + * issues related to the validation of glTF extensions. + */ +export class GltfExtensionValidationIssues { + /** + * Indicates that the glTF was fundamentally invalid + * (i.e. generally not a valid GLB/glTF asset at all) + * + * @param path - The path for the `ValidationIssue` + * @param message - The message for the `ValidationIssue` + * @returns The `ValidationIssue` + */ + static GLTF_INVALID(path: string, message: string) { + const type = "GLTF_INVALID"; + const severity = ValidationIssueSeverity.ERROR; + const issue = new ValidationIssue(type, path, message, severity); + return issue; + } +} diff --git a/src/tileFormats/GltfValidator.ts b/src/tileFormats/GltfValidator.ts index 794a5713..c2502138 100644 --- a/src/tileFormats/GltfValidator.ts +++ b/src/tileFormats/GltfValidator.ts @@ -5,6 +5,7 @@ import { ValidationContext } from "../validation/ValidationContext"; import { ValidationIssue } from "../validation/ValidationIssue"; import { ContentValidationIssues } from "../issues/ContentValidationIssues"; +import { GltfExtensionValidators } from "../validation/gltfExtensions/GltfExtensionValidators"; // eslint-disable-next-line @typescript-eslint/no-var-requires const validator = require("gltf-validator"); @@ -175,6 +176,13 @@ export class GltfValidator implements Validator { context.addIssue(issue); } + // XXX TODO Find a sensible place to hook in glTF extension validators + const extensionsValid = + await GltfExtensionValidators.validateGltfExtensions(uri, input, context); + if (!extensionsValid) { + return false; + } + return true; } } diff --git a/src/validation/gltfExtensions/ExtMeshFeaturesValidator.ts b/src/validation/gltfExtensions/ExtMeshFeaturesValidator.ts new file mode 100644 index 00000000..08f28565 --- /dev/null +++ b/src/validation/gltfExtensions/ExtMeshFeaturesValidator.ts @@ -0,0 +1,162 @@ +import { defined } from "3d-tiles-tools"; + +import { ValidationContext } from "./../ValidationContext"; +import { BasicValidator } from "./../BasicValidator"; + +/** + * A class for validating the `EXT_mesh_features` extension in + * glTF assets. + * + * @internal + */ +export class ExtMeshFeaturesValidator { + /** + * Performs the validation to ensure that the `EXT_mesh_features` + * extensions in the given glTF are valid + * + * @param path - The path for validation issues + * @param gltf - The object to validate + * @param context - The `ValidationContext` that any issues will be added to + * @returns Whether the object was valid + */ + static validateGltf( + path: string, + gltf: any, + context: ValidationContext + ): boolean { + const meshes = gltf.meshes; + if (!meshes) { + return true; + } + if (!Array.isArray(meshes)) { + return true; + } + + let result = true; + for (const mesh of meshes) { + const primitives = mesh.primitives; + if (!primitives) { + continue; + } + if (!Array.isArray(primitives)) { + continue; + } + for (const primitive of primitives) { + if (!primitive) { + continue; + } + const extensions = primitive.extensions; + if (!extensions) { + continue; + } + const extensionNames = Object.keys(extensions); + for (const extensionName of extensionNames) { + if (extensionName === "EXT_mesh_features") { + const extensionObject = extensions[extensionName]; + const objectIsValid = + ExtMeshFeaturesValidator.validateExtMeshFeatures( + path, + extensionObject, + context + ); + if (!objectIsValid) { + result = false; + } + } + } + } + } + return result; + } + + private static validateExtMeshFeatures( + path: string, + meshFeatures: any, + context: ValidationContext + ) { + // Make sure that the given value is an object + if ( + !BasicValidator.validateObject( + path, + "meshFeatures", + meshFeatures, + context + ) + ) { + return false; + } + + let result = true; + + // Validate the featureIds + const featureIds = meshFeatures.featureIds; + const featureIdsPath = path + "/featureIds"; + if (defined(featureIds)) { + // The featureIds MUST be an array of at least 1 objects + if ( + !BasicValidator.validateArray( + featureIdsPath, + "featureIds", + featureIds, + 1, + undefined, + "object", + context + ) + ) { + result = false; + } else { + // Validate each featureId + for (let i = 0; i < featureIds.length; i++) { + const featureId = featureIds[i]; + const featureIdPath = featureIdsPath + "/" + i; + if ( + !ExtMeshFeaturesValidator.validateFeatureId( + featureIdPath, + featureId, + context + ) + ) { + result = false; + } + } + } + } + return result; + } + + private static validateFeatureId( + path: string, + featureId: any, + context: ValidationContext + ) { + // Make sure that the given value is an object + if (!BasicValidator.validateObject(path, "featureId", featureId, context)) { + return false; + } + + let result = true; + + // Validate the featureCount + // The featureCount MUST be defined + // The featureCount MUST be an integer of at least 0 + const featureCount = featureId.featureCount; + const featureCountPath = path + "/featureCount"; + if ( + !BasicValidator.validateIntegerRange( + featureCountPath, + "featureCount", + featureCount, + 0, + true, + undefined, + false, + context + ) + ) { + result = false; + } + + return result; + } +} diff --git a/src/validation/gltfExtensions/GltfExtensionValidators.ts b/src/validation/gltfExtensions/GltfExtensionValidators.ts new file mode 100644 index 00000000..2f4cc245 --- /dev/null +++ b/src/validation/gltfExtensions/GltfExtensionValidators.ts @@ -0,0 +1,67 @@ +import { GltfExtensionValidationIssues } from "../../issues/GltfExtensionValidationIssues"; +import { IoValidationIssues } from "../../issues/IoValidationIssue"; +import { ValidationContext } from "../ValidationContext"; + +import { Buffers, GltfUtilities } from "3d-tiles-tools"; +import { ExtMeshFeaturesValidator } from "./ExtMeshFeaturesValidator"; + +export class GltfExtensionValidators { + static async validateGltfExtensions( + path: string, + input: Buffer, + context: ValidationContext + ): Promise { + const gltf = await GltfExtensionValidators.readGltfObject( + path, + input, + context + ); + if (!gltf) { + // Issue was already added to context + return false; + } + + let result = true; + + // Validate `EXT_mesh_features` + if (!ExtMeshFeaturesValidator.validateGltf(path, gltf, context)) { + result = false; + } + return result; + } + + private static async readGltfObject( + path: string, + input: Buffer, + context: ValidationContext + ): Promise { + // Assume that the input contains glTF JSON, but... + let gltfJsonBuffer: Buffer | undefined = input; + + // ... if the input starts with "glTF", then try to + // extract the JSON from the GLB: + const magicString = Buffers.getMagicString(input); + if (magicString === "glTF") { + try { + gltfJsonBuffer = GltfUtilities.extractJsonFromGlb(input); + } catch (error) { + // A TileFormatError may be thrown here + const message = `Could not extract JSON from GLB: ${error}`; + const issue = GltfExtensionValidationIssues.GLTF_INVALID(path, message); + context.addIssue(issue); + return undefined; + } + } + + let gltf: any = undefined; + try { + gltf = JSON.parse(gltfJsonBuffer.toString()); + } catch (error) { + const message = `Could not parse glTF JSON: ${error}`; + const issue = IoValidationIssues.JSON_PARSE_ERROR(path, message); + context.addIssue(issue); + return undefined; + } + return gltf; + } +}