diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ec6e1d..75fbc66b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ### New Features +* Added full support for `KHR_animation_pointer` extension. + * Added new `KHR_ANIMATION_POINTER_CANNOT_VALIDATE_EXTRAS`, `KHR_ANIMATION_POINTER_INVALID_POINTER`, `KHR_ANIMATION_POINTER_INTEGER_NON_STEP_INTERPOLATION`, `KHR_ANIMATION_POINTER_COMPONENT_TYPE_MISMATCH`, and `KHR_ANIMATION_POINTER_VECTOR_SIZE_MISMATCH` validation errors. + * The `INCOMPLETE_EXTENSION_SUPPORT` validation info will now be reported far less often, only for unknown extensions. + * Extensions can define validation for their own pointer targets by implementing the `validateExtensionPointer` method in their extension class. + * Added support for the `KHR_node_visibility` extension. * Added new `NODE_SKINNED_MESH_PARENT_TRANSFORMS` validation warning. diff --git a/ISSUES.md b/ISSUES.md index b81e4772..9afae3c4 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -43,6 +43,11 @@ |INVALID_GL_VALUE|Invalid value `%1` for GL type '`%2`'.|Error| |KHR_ANIMATION_POINTER_ANIMATION_CHANNEL_TARGET_NODE|This extension requires the animation channel target node to be undefined.|Error| |KHR_ANIMATION_POINTER_ANIMATION_CHANNEL_TARGET_PATH|This extension requires the animation channel target path to be 'pointer'. Found '`%1`' instead.|Error| +|KHR_ANIMATION_POINTER_INTEGER_NON_STEP_INTERPOLATION|Animation samplers used with int or bool Object Model Data Types MUST use STEP interpolation. Pointer: '`%1`', expected: '`%2`', found: '`%3`'.|Error| +|KHR_ANIMATION_POINTER_CANNOT_VALIDATE_EXTRAS|Cannot validate pointers into non-standardized extras.|Hint| +|KHR_ANIMATION_POINTER_INVALID_POINTER|The pointer '`%1`' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.|Error| +|KHR_ANIMATION_POINTER_COMPONENT_TYPE_MISMATCH|The output component type of the accessor does not match the required type of the glTF Object Model property being targeted by the pointer '`%1`'. Expected type: '`%2`', found type: '`%3`'.|Error| +|KHR_ANIMATION_POINTER_VECTOR_SIZE_MISMATCH|The output vector size of the accessor does not match the required vector size of the glTF Object Model property being targeted by the pointer '`%1`'. Expected size: `%2`, found size: `%3`.|Error| |KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES|outerConeAngle (`%2`) is less than or equal to innerConeAngle (`%1`).|Error| |KHR_MATERIALS_ANISOTROPY_ANISOTROPY_TEXTURE_TEXCOORD|Normal and anisotropy textures should use the same texture coords.|Warning| |KHR_MATERIALS_CLEARCOAT_CLEARCOAT_NORMAL_TEXTURE_TEXCOORD|Normal and clearcoat normal textures should use the same texture coords.|Warning| diff --git a/lib/src/base/animation.dart b/lib/src/base/animation.dart index 853808b1..e5785433 100644 --- a/lib/src/base/animation.dart +++ b/lib/src/base/animation.dart @@ -311,14 +311,13 @@ class AnimationChannel extends GltfProperty { if (context.validate) { checkMembers(map, ANIMATION_CHANNEL_MEMBERS, context); } - - return AnimationChannel._( - getIndex(map, SAMPLER, context), - getObjectFromInnerMap( - map, TARGET, context, AnimationChannelTarget.fromMap, - req: true), - getExtensions(map, AnimationChannel, context), - getExtras(map, context)); + final target = getObjectFromInnerMap( + map, TARGET, context, AnimationChannelTarget.fromMap, + req: true); + final channel = AnimationChannel._(getIndex(map, SAMPLER, context), target, + getExtensions(map, AnimationChannel, context), getExtras(map, context)); + context.registerObjectsOwner(channel, [target]); + return channel; } } diff --git a/lib/src/context.dart b/lib/src/context.dart index 56576d6b..e527bfea 100644 --- a/lib/src/context.dart +++ b/lib/src/context.dart @@ -54,6 +54,29 @@ class Context { static Iterable get defaultExtensionNames => kDefaultExtensions.map((e) => e.name); + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + for (final extension in context._userExtensions) { + final func = extension.validateExtensionPointer; + if (func != null) { + final result = func(context, pointer, prop, inPropDepth); + if (result != PointerValidity.unknown) { + return result; + } + } + } + for (final extension in kDefaultExtensions) { + final func = extension.validateExtensionPointer; + if (func != null) { + final result = func(context, pointer, prop, inPropDepth); + if (result != PointerValidity.unknown) { + return result; + } + } + } + return PointerValidity.unknown; + } + Context({this.validate = true, ValidationOptions options}) : options = options ?? ValidationOptions() { _extensionsLoadedView = UnmodifiableListView(_extensionsLoaded); diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 39e23132..a25b70dc 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -502,6 +502,43 @@ class SemanticError extends IssueType { (args) => 'This extension requires the animation channel target path ' "to be 'pointer'. Found ${_q(args[0])} instead."); + static final SemanticError khrAnimationPointerCannotValidateExtras = + SemanticError._( + 'KHR_ANIMATION_POINTER_CANNOT_VALIDATE_EXTRAS', + (args) => 'Cannot validate pointers into non-standardized extras.', + Severity.Hint); + + static final SemanticError khrAnimationPointerInvalidPointer = + SemanticError._( + 'KHR_ANIMATION_POINTER_INVALID_POINTER', + (args) => 'The pointer ${_q(args[0])} does not point to a valid ' + 'property in the glTF JSON. Animated properties must be ' + 'defined in the JSON document.'); + + static final SemanticError khrAnimationPointerIntegerNonStepInterpolation = + SemanticError._( + 'KHR_ANIMATION_POINTER_INTEGER_NON_STEP_INTERPOLATION', + (args) => 'Animation samplers used with int or bool Object Model ' + 'Data Types MUST use STEP interpolation. ' + 'Pointer: ${_q(args[0])}, expected: ${_q(args[1])}, ' + 'found: ${_q(args[2])}.'); + + static final SemanticError khrAnimationPointerComponentTypeMismatch = + SemanticError._( + 'KHR_ANIMATION_POINTER_COMPONENT_TYPE_MISMATCH', + (args) => 'The output component type of the accessor does not ' + 'match the required type of the glTF Object Model property ' + 'being targeted by the pointer ${_q(args[0])}. ' + 'Expected type: ${_q(args[1])}, found type: ${_q(args[2])}.'); + + static final SemanticError khrAnimationPointerVectorSizeMismatch = + SemanticError._( + 'KHR_ANIMATION_POINTER_VECTOR_SIZE_MISMATCH', + (args) => 'The output vector size of the accessor does not match ' + 'the required vector size of the glTF Object Model property ' + 'being targeted by the pointer ${_q(args[0])}. ' + 'Expected size: ${args[1]}, found size: ${args[2]}.'); + static final SemanticError khrLightsPunctualLightSpotAngles = SemanticError._( 'KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES', (args) => 'outerConeAngle (${args[1]}) is less than or equal to ' diff --git a/lib/src/ext/KHR_animation_pointer/khr_animation_pointer.dart b/lib/src/ext/KHR_animation_pointer/khr_animation_pointer.dart index 57c7026c..c952b8b3 100644 --- a/lib/src/ext/KHR_animation_pointer/khr_animation_pointer.dart +++ b/lib/src/ext/KHR_animation_pointer/khr_animation_pointer.dart @@ -15,6 +15,23 @@ const List KHR_ANIMATION_POINTER_MEMBERS = [ POINTER, ]; +// Spec: "If the Object Model Data Type is one of the float* types, the output +// accessor values are converted based on the accessor's component type". +// So float properties only need their vector size checked, while integer and +// boolean properties need to be scalar and type checked. +enum PointerValidity { + invalid, + unknown, + extra, + validIfBool, + validIfInt, + validIfScalar, + validIfScalarArray, + validIfVec2, + validIfVec3, + validIfVec4, +} + class KhrAnimationPointer extends GltfProperty { final String pointer; @@ -40,10 +57,7 @@ class KhrAnimationPointer extends GltfProperty { @override void link(Gltf gltf, Context context) { // TODO: - // * pointer existence // * channel target uniqueness - // * output accessor compatibility - context.addIssue(LinkError.incompleteExtensionSupport); Object o = this; while (o != null) { @@ -61,6 +75,527 @@ class KhrAnimationPointer extends GltfProperty { break; } } + if (o == null) { + return; + } + if (pointer == null) { + return; + } + // Retrieve the channel and its sampler. + final animChannel = context.owners[o] as AnimationChannel; + final sampler = animChannel.sampler; + if (sampler == null) { + return; // The animation parser will report this issue. + } + final outputAccessor = sampler.output; + if (outputAccessor == null) { + return; // The animation parser will report this issue. + } + final compType = outputAccessor.componentType; + // Validate pointer existence. + final parts = pointer.split('/'); + final validity = _validatePointerExistence(context, parts, gltf); + // Validate if the type is compatible if it exists. + switch (validity) { + case PointerValidity.invalid: + context.addIssue(SemanticError.khrAnimationPointerInvalidPointer, + args: [pointer]); + break; + case PointerValidity.unknown: + context.addIssue(LinkError.incompleteExtensionSupport); + break; + case PointerValidity.extra: + context.addIssue(SemanticError.khrAnimationPointerCannotValidateExtras, + args: [pointer]); + break; + case PointerValidity.validIfBool: + // Spec: "If the Object Model Data Type is bool, the output accessor + // component type MUST be unsigned byte" + if (compType != 5121) { + context.addIssue( + SemanticError.khrAnimationPointerComponentTypeMismatch, + args: [pointer, '5121 (unsigned byte)', compType]); + } + if (outputAccessor.type != SCALAR) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'SCALAR', outputAccessor.type]); + } + // Spec: "Animation samplers used with int or bool Object Model + // Data Types MUST use STEP interpolation." + if (sampler.interpolation != STEP) { + context.addIssue( + SemanticError.khrAnimationPointerIntegerNonStepInterpolation, + args: [pointer, STEP, sampler.interpolation]); + } + break; + case PointerValidity.validIfInt: + // Spec: "If the Object Model Data Type is int, the output accessor + // component type MUST be one of the non-normalized integer types" + if (outputAccessor.normalized) { + context.addIssue( + SemanticError.khrAnimationPointerComponentTypeMismatch, + args: [pointer, 'non-normalized integer type', compType]); + } + switch (compType) { + // Core glTF integer types: + case 5120: // BYTE (signed) + case 5121: // UNSIGNED_BYTE + case 5122: // SHORT (signed) + case 5123: // UNSIGNED_SHORT + case 5125: // UNSIGNED_INT + break; + // Other OpenGL integer types provided by glTF extensions: + case 5124: // INT (signed) + case 5134: // INT64 + case 5135: // UNSIGNED_INT64 + // TODO: Validate that the corresponding extensions are present. + context.addIssue(LinkError.incompleteExtensionSupport); + break; + default: + context.addIssue( + SemanticError.khrAnimationPointerComponentTypeMismatch, + args: [pointer, 'non-normalized integer type', compType]); + } + if (outputAccessor.type != SCALAR) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'SCALAR', outputAccessor.type]); + } + if (sampler.interpolation != STEP) { + context.addIssue( + SemanticError.khrAnimationPointerIntegerNonStepInterpolation, + args: [pointer, STEP, sampler.interpolation]); + } + break; + // Spec: "If the Object Model Data Type is one of the float* types, + // the output accessor values are converted based on the accessor's + // component type" so any component type is valid for float properties. + case PointerValidity.validIfScalar: + if (outputAccessor.type != SCALAR) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'SCALAR', outputAccessor.type]); + } + break; + case PointerValidity.validIfScalarArray: + // TODO: Validate array length somehow. + if (outputAccessor.type != SCALAR) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'SCALAR', outputAccessor.type]); + } + break; + case PointerValidity.validIfVec2: + if (outputAccessor.type != VEC2) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'VEC2', outputAccessor.type]); + } + break; + case PointerValidity.validIfVec3: + if (outputAccessor.type != VEC3) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'VEC3', outputAccessor.type]); + } + break; + case PointerValidity.validIfVec4: + if (outputAccessor.type != VEC4) { + context.addIssue(SemanticError.khrAnimationPointerVectorSizeMismatch, + args: [pointer, 'VEC4', outputAccessor.type]); + } + break; + } + } + + // Check if pointer points to a property that exists in the glTF file. + static PointerValidity _validatePointerExistence( + Context context, List pointer, Gltf gltf) { + if (pointer == null || pointer.length < 3) { + return PointerValidity.invalid; + } + // Pointer must start with a slash, so the split first element is ''. + if (pointer[0] != '') { + return PointerValidity.invalid; + } + if (pointer[1] == EXTENSIONS || pointer[1] == EXTRAS) { + return _validateExtensionsExtras(context, pointer, gltf, 1); + } + // Aside from extras, all valid pointers are at least 3 levels deep (len 4). + if (pointer.length < 4) { + return PointerValidity.invalid; + } + final itemProperty = pointer[3]; + if (pointer[1] == ASSET) { + // There are no core glTF pointers directly in asset, + // so the only possible valid case is a pointer into extensions/extras. + return _validateExtensionsExtras(context, pointer, gltf.asset, 2); + } + // Everything below here addresses top-level glTF arrays. + final topLevelIndex = int.tryParse(pointer[2]); + if (topLevelIndex == null || topLevelIndex < 0) { + return PointerValidity.invalid; + } + if (pointer[1] == ACCESSORS) { + if (topLevelIndex >= gltf.accessors.length) { + return PointerValidity.invalid; + } + final accessor = gltf.accessors[topLevelIndex]; + // There are no core glTF pointers directly in accessors, + // so the only possible valid case is a pointer into extensions. + if (itemProperty == SPARSE && pointer.length > 5) { + // Shortest possible pointer into accessor sparse: + // `/accessors/0/sparse/extras/someExtra` + // Remember that this has a length of 6, the first is an empty string. + final sparse = accessor.sparse; + if (sparse == null) { + return PointerValidity.invalid; + } + final subProperty = pointer[4]; + if (subProperty == INDICES) { + final indices = sparse.indices; + if (indices == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, indices, 5); + } + if (subProperty == VALUES) { + final values = sparse.values; + if (values == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, values, 5); + } + return _validateExtensionsExtras(context, pointer, sparse, 4); + } + return _validateExtensionsExtras(context, pointer, accessor, 3); + } else if (pointer[1] == ANIMATIONS) { + if (topLevelIndex >= gltf.animations.length) { + return PointerValidity.invalid; + } + final animation = gltf.animations[topLevelIndex]; + // There are no core glTF pointers directly in animations, + // so the only possible valid case is a pointer into extensions. + if (pointer.length > 6) { + // Shortest possible pointer into channels/samplers: + // `/animations/0/channels/0/extras/someExtra` + if (itemProperty == CHANNELS) { + final channelIndex = int.tryParse(pointer[4]); + if (channelIndex == null || + channelIndex < 0 || + channelIndex >= animation.channels.length) { + return PointerValidity.invalid; + } + final channel = animation.channels[channelIndex]; + if (pointer[5] == TARGET) { + final target = channel.target; + if (target == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, target, 6); + } + return _validateExtensionsExtras(context, pointer, channel, 5); + } + if (itemProperty == SAMPLERS) { + final samplerIndex = int.tryParse(pointer[4]); + if (samplerIndex == null || + samplerIndex < 0 || + samplerIndex >= animation.samplers.length) { + return PointerValidity.invalid; + } + final sampler = animation.samplers[samplerIndex]; + if (sampler == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, sampler, 5); + } + } + return _validateExtensionsExtras(context, pointer, animation, 3); + } else if (pointer[1] == BUFFERS) { + if (topLevelIndex >= gltf.buffers.length) { + return PointerValidity.invalid; + } + final buffer = gltf.buffers[topLevelIndex]; + // There are no core glTF pointers directly in buffers, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, buffer, 3); + } else if (pointer[1] == BUFFER_VIEWS) { + if (topLevelIndex >= gltf.bufferViews.length) { + return PointerValidity.invalid; + } + final bufferView = gltf.bufferViews[topLevelIndex]; + // There are no core glTF pointers directly in buffer views, + // so the only possible valid case is a pointer into extensions/extras. + return _validateExtensionsExtras(context, pointer, bufferView, 3); + } else if (pointer[1] == CAMERAS) { + if (topLevelIndex >= gltf.cameras.length) { + return PointerValidity.invalid; + } + // All camera core pointers and extensions are at least 5 levels deep. + if (pointer.length < 5) { + return PointerValidity.invalid; + } + final camera = gltf.cameras[topLevelIndex]; + final subProperty = pointer[4]; + // Spec: "Pointers to the asset properties that do not have a + // spec-defined default value, such as /cameras/0/perspective/zfar, + // are invalid if the property is not defined in the asset explicitly." + // We can check for this by checking if the parsed value is NaN. + if (itemProperty == ORTHOGRAPHIC) { + final orthographic = camera.orthographic; + if (orthographic == null) { + return PointerValidity.invalid; + } + if (subProperty == XMAG && !orthographic.xmag.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == YMAG && !orthographic.ymag.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == ZFAR && !orthographic.zfar.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == ZNEAR && !orthographic.znear.isNaN) { + return PointerValidity.validIfScalar; + } + return _validateExtensionsExtras(context, pointer, orthographic, 4); + } else if (itemProperty == PERSPECTIVE) { + final perspective = camera.perspective; + if (perspective == null) { + return PointerValidity.invalid; + } + if (subProperty == ASPECT_RATIO && !perspective.aspectRatio.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == YFOV && !perspective.yfov.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == ZFAR && !perspective.zfar.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == ZNEAR && !perspective.znear.isNaN) { + return PointerValidity.validIfScalar; + } + return _validateExtensionsExtras(context, pointer, perspective, 4); + } + return _validateExtensionsExtras(context, pointer, camera, 3); + } else if (pointer[1] == IMAGES) { + if (topLevelIndex >= gltf.images.length) { + return PointerValidity.invalid; + } + final image = gltf.images[topLevelIndex]; + // There are no core glTF pointers directly in images, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, image, 3); + } else if (pointer[1] == MATERIALS) { + if (topLevelIndex >= gltf.materials.length) { + return PointerValidity.invalid; + } + final material = gltf.materials[topLevelIndex]; + if (itemProperty == ALPHA_CUTOFF && !material.alphaCutoff.isNaN) { + return PointerValidity.validIfScalar; + } + if (itemProperty == EMISSIVE_FACTOR && + material.emissiveFactor != null && + material.emissiveFactor.length == 3) { + return PointerValidity.validIfVec3; + } + // Remaining core properties and extensions are at least 5 levels deep. + if (pointer.length < 5) { + return PointerValidity.invalid; + } + final subProperty = pointer[4]; + if (itemProperty == EMISSIVE_TEXTURE) { + final emissiveTex = material.emissiveTexture; + if (emissiveTex == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, emissiveTex, 4); + } + if (itemProperty == NORMAL_TEXTURE) { + final normTex = material.normalTexture; + if (normTex == null) { + return PointerValidity.invalid; + } + if (subProperty == 'scale' && !normTex.scale.isNaN) { + return PointerValidity.validIfScalar; + } + return _validateExtensionsExtras(context, pointer, normTex, 4); + } + if (itemProperty == OCCLUSION_TEXTURE) { + final occTex = material.occlusionTexture; + if (occTex == null) { + return PointerValidity.invalid; + } + if (subProperty == 'strength' && !occTex.strength.isNaN) { + return PointerValidity.validIfScalar; + } + return _validateExtensionsExtras(context, pointer, occTex, 4); + } + if (itemProperty == PBR_METALLIC_ROUGHNESS) { + final pbr = material.pbrMetallicRoughness; + if (pbr == null) { + return PointerValidity.invalid; + } + if (subProperty == BASE_COLOR_FACTOR && + pbr.baseColorFactor != null && + pbr.baseColorFactor.length == 4) { + return PointerValidity.validIfVec4; + } + if (subProperty == BASE_COLOR_TEXTURE) { + final baseTex = pbr.baseColorTexture; + if (baseTex == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, baseTex, 4); + } + if (subProperty == METALLIC_FACTOR && !pbr.metallicFactor.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == ROUGHNESS_FACTOR && !pbr.roughnessFactor.isNaN) { + return PointerValidity.validIfScalar; + } + if (subProperty == METALLIC_ROUGHNESS_TEXTURE) { + final mrTex = pbr.metallicRoughnessTexture; + if (mrTex == null) { + return PointerValidity.invalid; + } + return _validateExtensionsExtras(context, pointer, mrTex, 4); + } + return _validateExtensionsExtras(context, pointer, pbr, 4); + } + return _validateExtensionsExtras(context, pointer, material, 3); + } else if (pointer[1] == MESHES) { + if (topLevelIndex >= gltf.meshes.length) { + return PointerValidity.invalid; + } + final mesh = gltf.meshes[topLevelIndex]; + if (itemProperty == PRIMITIVES) { + if (mesh.primitives.isEmpty) { + return PointerValidity.invalid; + } + // Shortest possible pointer into primitives: + // `/meshes/0/primitives/0/extras/someExtra` + // Remember that this has a length of 7, the first is an empty string. + if (pointer.length < 7) { + return PointerValidity.invalid; + } + final primIndex = int.tryParse(pointer[4]); + if (primIndex == null || + primIndex < 0 || + primIndex >= mesh.primitives.length) { + return PointerValidity.invalid; + } + final primitive = mesh.primitives[primIndex]; + return _validateExtensionsExtras(context, pointer, primitive, 5); + } + if (itemProperty == WEIGHTS) { + if (pointer.length == 4) { + return PointerValidity.validIfScalarArray; + } else if (pointer.length == 5) { + final weightIndex = int.tryParse(pointer[4]); + if (weightIndex == null || weightIndex < 0) { + return PointerValidity.invalid; + } + if (mesh.weights != null && weightIndex < mesh.weights.length) { + return PointerValidity.validIfScalar; + } + return PointerValidity.invalid; + } else { + return PointerValidity.invalid; + } + } + return _validateExtensionsExtras(context, pointer, mesh, 3); + } else if (pointer[1] == NODES) { + if (topLevelIndex >= gltf.nodes.length) { + return PointerValidity.invalid; + } + final node = gltf.nodes[topLevelIndex]; + if (pointer.length == 4) { + if (itemProperty == TRANSLATION) { + return PointerValidity.validIfVec3; + } + if (itemProperty == ROTATION) { + return PointerValidity.validIfVec4; + } + if (itemProperty == SCALE) { + return PointerValidity.validIfVec3; + } + } + if (itemProperty == WEIGHTS) { + if (pointer.length == 4) { + return PointerValidity.validIfScalarArray; + } else if (pointer.length == 5) { + final weightIndex = int.tryParse(pointer[4]); + if (weightIndex == null || weightIndex < 0) { + return PointerValidity.invalid; + } + if (node.weights != null && weightIndex < node.weights.length) { + return PointerValidity.validIfScalar; + } + return PointerValidity.invalid; + } else { + return PointerValidity.invalid; + } + } + return _validateExtensionsExtras(context, pointer, node, 3); + } else if (pointer[1] == SAMPLERS) { + if (topLevelIndex >= gltf.samplers.length) { + return PointerValidity.invalid; + } + final sampler = gltf.samplers[topLevelIndex]; + // There are no core glTF pointers directly in samplers, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, sampler, 3); + } else if (pointer[1] == SCENES) { + if (topLevelIndex >= gltf.scenes.length) { + return PointerValidity.invalid; + } + final scene = gltf.scenes[topLevelIndex]; + // There are no mutable core glTF pointers directly in scenes, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, scene, 3); + } else if (pointer[1] == SKINS) { + if (topLevelIndex >= gltf.skins.length) { + return PointerValidity.invalid; + } + final skin = gltf.skins[topLevelIndex]; + // There are no mutable core glTF pointers directly in skins, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, skin, 3); + } else if (pointer[1] == TEXTURES) { + if (topLevelIndex >= gltf.textures.length) { + return PointerValidity.invalid; + } + final texture = gltf.textures[topLevelIndex]; + // There are no core glTF pointers directly in textures, + // so the only possible valid case is a pointer into extensions. + return _validateExtensionsExtras(context, pointer, texture, 3); + } + return PointerValidity.invalid; + } + + // Validate pointers into extensions and extras. + // This is always the last resort after checking everything else. + static PointerValidity _validateExtensionsExtras(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer.length < inPropDepth + 2) { + return PointerValidity.invalid; + } + final itemProperty = pointer[inPropDepth]; + // Extensions must be at least +2 levels deep: prop/extensions/extName/here. + if (itemProperty == EXTENSIONS && pointer.length >= inPropDepth + 2) { + return Context.validateExtensionPointer( + context, pointer, prop, inPropDepth); + } + if (itemProperty == EXTRAS) { + // Spec: "Properties located in extras objects MAY be + // targeted as well, but validity and interpretation of the + // animated values is entirely application specific." + // So the only validation we can do is check if the key exists. + if (prop.extras is Map) { + final extrasMap = prop.extras as Map; + if (extrasMap.containsKey(pointer[inPropDepth + 1])) { + return PointerValidity.extra; + } + } + } + return PointerValidity.invalid; } } diff --git a/lib/src/ext/KHR_lights_punctual/khr_lights_punctual.dart b/lib/src/ext/KHR_lights_punctual/khr_lights_punctual.dart index 3275b1e2..af6f98f8 100644 --- a/lib/src/ext/KHR_lights_punctual/khr_lights_punctual.dart +++ b/lib/src/ext/KHR_lights_punctual/khr_lights_punctual.dart @@ -19,12 +19,7 @@ library gltf.extensions.khr_lights_punctual; import 'package:gltf/src/base/gltf_property.dart'; import 'package:gltf/src/ext/extensions.dart'; -const Extension khrLightsPunctualExtension = - Extension('KHR_lights_punctual', { - Gltf: ExtensionDescriptor(KhrLightsPunctualGltf.fromMap), - Node: ExtensionDescriptor(KhrLightsPunctualNode.fromMap) -}); - +const String KHR_LIGHTS_PUNCTUAL = 'KHR_lights_punctual'; const String LIGHTS = 'lights'; const String LIGHT = 'light'; const String COLOR = 'color'; @@ -109,6 +104,64 @@ class KhrLightsPunctualGltf extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer.length < inPropDepth + 5) { + return PointerValidity.unknown; + } + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_LIGHTS_PUNCTUAL || + pointer[inPropDepth + 2] != LIGHTS) { + return PointerValidity.unknown; + } + if (prop.extensions == null || + !prop.extensions.containsKey(KHR_LIGHTS_PUNCTUAL)) { + return PointerValidity.invalid; + } + final lightsExtension = + prop.extensions[KHR_LIGHTS_PUNCTUAL] as KhrLightsPunctualGltf; + final lightIndex = int.tryParse(pointer[inPropDepth + 3]); + if (lightIndex == null || + lightIndex < 0 || + lightIndex >= lightsExtension.lights.length) { + return PointerValidity.invalid; + } + final light = lightsExtension.lights[lightIndex]; + final subProp = pointer[inPropDepth + 4]; + switch (subProp) { + case COLOR: + if (light.color == null || light.color.length != 3) { + return PointerValidity.invalid; + } + return PointerValidity.validIfVec3; + case INTENSITY: + if (light.intensity.isNaN) { + return PointerValidity.invalid; + } + return PointerValidity.validIfScalar; + case RANGE: + if (light.range.isNaN || light.type == DIRECTIONAL) { + return PointerValidity.invalid; + } + return PointerValidity.validIfScalar; + case SPOT: + if (light.type != SPOT || light.spot == null) { + return PointerValidity.invalid; + } + if (pointer.length < inPropDepth + 6) { + return PointerValidity.invalid; + } + final spotSubProp = pointer[inPropDepth + 5]; + switch (spotSubProp) { + case INNER_CONE_ANGLE: + case OUTER_CONE_ANGLE: + return PointerValidity.validIfScalar; + } + return PointerValidity.unknown; + } + return PointerValidity.unknown; + } } class KhrLightsPunctualLight extends GltfChildOfRootProperty { @@ -239,3 +292,11 @@ class KhrLightsPunctualNode extends GltfProperty { KhrLightsPunctualLight get light => _light; } + +const Extension khrLightsPunctualExtension = Extension( + KHR_LIGHTS_PUNCTUAL, + { + Gltf: ExtensionDescriptor(KhrLightsPunctualGltf.fromMap), + Node: ExtensionDescriptor(KhrLightsPunctualNode.fromMap) + }, + validateExtensionPointer: KhrLightsPunctualGltf.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_anisotropy/khr_materials_anisotropy.dart b/lib/src/ext/KHR_materials_anisotropy/khr_materials_anisotropy.dart index 2efde5a5..e1245a1f 100644 --- a/lib/src/ext/KHR_materials_anisotropy/khr_materials_anisotropy.dart +++ b/lib/src/ext/KHR_materials_anisotropy/khr_materials_anisotropy.dart @@ -79,9 +79,46 @@ class KhrMaterialsAnisotropy extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_ANISOTROPY) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_ANISOTROPY)) { + return PointerValidity.invalid; + } + final anisoExt = + mat.extensions[KHR_MATERIALS_ANISOTROPY] as KhrMaterialsAnisotropy; + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case ANISOTROPY_STRENGTH: + case ANISOTROPY_ROTATION: + return PointerValidity.validIfScalar; + case ANISOTROPY_TEXTURE: + if (anisoExt.anisotropyTexture == null) { + return PointerValidity.invalid; + } + if (pointer.length < inPropDepth + 6) { + return PointerValidity.unknown; + } + // KHR_materials_anisotropy can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, anisoExt.anisotropyTexture, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsAnisotropyExtension = Extension( - KHR_MATERIALS_ANISOTROPY, { - Material: ExtensionDescriptor(KhrMaterialsAnisotropy.fromMap) -}); + KHR_MATERIALS_ANISOTROPY, + { + Material: ExtensionDescriptor(KhrMaterialsAnisotropy.fromMap) + }, + validateExtensionPointer: KhrMaterialsAnisotropy.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_clearcoat/khr_materials_clearcoat.dart b/lib/src/ext/KHR_materials_clearcoat/khr_materials_clearcoat.dart index f78efea6..4f7a56e5 100644 --- a/lib/src/ext/KHR_materials_clearcoat/khr_materials_clearcoat.dart +++ b/lib/src/ext/KHR_materials_clearcoat/khr_materials_clearcoat.dart @@ -114,9 +114,67 @@ class KhrMaterialsClearcoat extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_CLEARCOAT) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_CLEARCOAT)) { + return PointerValidity.invalid; + } + final clearcoatExt = + mat.extensions[KHR_MATERIALS_CLEARCOAT] as KhrMaterialsClearcoat; + final subProp = pointer[inPropDepth + 2]; + TextureInfo texInfoCheckTransform; + switch (subProp) { + case CLEARCOAT_FACTOR: + case CLEARCOAT_ROUGHNESS_FACTOR: + return PointerValidity.validIfScalar; + case CLEARCOAT_NORMAL_TEXTURE: + if (clearcoatExt.clearcoatNormalTexture == null) { + return PointerValidity.invalid; + } + if (pointer.length < inPropDepth + 4) { + return PointerValidity.invalid; + } + final texSubProp = pointer[inPropDepth + 3]; + if (texSubProp == SCALE) { + return PointerValidity.validIfScalar; + } + texInfoCheckTransform = clearcoatExt.clearcoatNormalTexture; + break; + case CLEARCOAT_TEXTURE: + if (clearcoatExt.clearcoatTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = clearcoatExt.clearcoatTexture; + break; + case CLEARCOAT_ROUGHNESS_TEXTURE: + if (clearcoatExt.clearcoatRoughnessTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = clearcoatExt.clearcoatRoughnessTexture; + break; + } + if (texInfoCheckTransform != null) { + // KHR_materials_clearcoat can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, texInfoCheckTransform, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsClearcoatExtension = Extension( - KHR_MATERIALS_CLEARCOAT, { - Material: ExtensionDescriptor(KhrMaterialsClearcoat.fromMap) -}); + KHR_MATERIALS_CLEARCOAT, + { + Material: ExtensionDescriptor(KhrMaterialsClearcoat.fromMap) + }, + validateExtensionPointer: KhrMaterialsClearcoat.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_dispersion/khr_materials_dispersion.dart b/lib/src/ext/KHR_materials_dispersion/khr_materials_dispersion.dart index ee720a15..388d9155 100644 --- a/lib/src/ext/KHR_materials_dispersion/khr_materials_dispersion.dart +++ b/lib/src/ext/KHR_materials_dispersion/khr_materials_dispersion.dart @@ -55,9 +55,33 @@ class KhrMaterialsDispersion extends GltfProperty { } } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_DISPERSION) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_DISPERSION)) { + return PointerValidity.invalid; + } + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case DISPERSION: + return PointerValidity.validIfScalar; + } + return PointerValidity.unknown; + } } const Extension khrMaterialsDispersionExtension = Extension( - KHR_MATERIALS_DISPERSION, { - Material: ExtensionDescriptor(KhrMaterialsDispersion.fromMap) -}); + KHR_MATERIALS_DISPERSION, + { + Material: ExtensionDescriptor(KhrMaterialsDispersion.fromMap) + }, + validateExtensionPointer: KhrMaterialsDispersion.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_emissive_strength/khr_materials_emissive_strength.dart b/lib/src/ext/KHR_materials_emissive_strength/khr_materials_emissive_strength.dart index 7b0d60bd..e37f7e55 100644 --- a/lib/src/ext/KHR_materials_emissive_strength/khr_materials_emissive_strength.dart +++ b/lib/src/ext/KHR_materials_emissive_strength/khr_materials_emissive_strength.dart @@ -60,9 +60,34 @@ class KhrMaterialsEmissiveStrength extends GltfProperty { } } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_EMISSIVE_STRENGTH) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_EMISSIVE_STRENGTH)) { + return PointerValidity.invalid; + } + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case EMISSIVE_STRENGTH: + return PointerValidity.validIfScalar; + } + return PointerValidity.unknown; + } } const Extension khrMaterialsEmissiveStrengthExtension = Extension( - KHR_MATERIALS_EMISSIVE_STRENGTH, { - Material: ExtensionDescriptor(KhrMaterialsEmissiveStrength.fromMap) -}); + KHR_MATERIALS_EMISSIVE_STRENGTH, + { + Material: ExtensionDescriptor(KhrMaterialsEmissiveStrength.fromMap) + }, + validateExtensionPointer: + KhrMaterialsEmissiveStrength.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_ior/khr_materials_ior.dart b/lib/src/ext/KHR_materials_ior/khr_materials_ior.dart index 95727a84..cc4858d4 100644 --- a/lib/src/ext/KHR_materials_ior/khr_materials_ior.dart +++ b/lib/src/ext/KHR_materials_ior/khr_materials_ior.dart @@ -32,9 +32,33 @@ class KhrMaterialsIor extends GltfProperty { return KhrMaterialsIor._(ior, extensions, getExtras(map, context)); } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_IOR) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_IOR)) { + return PointerValidity.invalid; + } + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case IOR: + return PointerValidity.validIfScalar; + } + return PointerValidity.unknown; + } } const Extension khrMaterialsIorExtension = Extension( - KHR_MATERIALS_IOR, { - Material: ExtensionDescriptor(KhrMaterialsIor.fromMap) -}); + KHR_MATERIALS_IOR, + { + Material: ExtensionDescriptor(KhrMaterialsIor.fromMap) + }, + validateExtensionPointer: KhrMaterialsIor.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_iridescence/khr_materials_iridescence.dart b/lib/src/ext/KHR_materials_iridescence/khr_materials_iridescence.dart index b5d24545..e79f5dc8 100644 --- a/lib/src/ext/KHR_materials_iridescence/khr_materials_iridescence.dart +++ b/lib/src/ext/KHR_materials_iridescence/khr_materials_iridescence.dart @@ -116,9 +116,56 @@ class KhrMaterialsIridescence extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_IRIDESCENCE) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_IRIDESCENCE)) { + return PointerValidity.invalid; + } + final iridescenceExt = + mat.extensions[KHR_MATERIALS_IRIDESCENCE] as KhrMaterialsIridescence; + final subProp = pointer[inPropDepth + 2]; + TextureInfo texInfoCheckTransform; + switch (subProp) { + case IRIDESCENCE_FACTOR: + case IRIDESCENCE_IOR: + case IRIDESCENCE_THICKNESS_MINIMUM: + case IRIDESCENCE_THICKNESS_MAXIMUM: + return PointerValidity.validIfScalar; + case IRIDESCENCE_TEXTURE: + if (iridescenceExt.iridescenceTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = iridescenceExt.iridescenceTexture; + break; + case IRIDESCENCE_THICKNESS_TEXTURE: + if (iridescenceExt.iridescenceThicknessTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = iridescenceExt.iridescenceThicknessTexture; + break; + } + if (texInfoCheckTransform != null) { + // KHR_materials_iridescence can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, texInfoCheckTransform, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsIridescenceExtension = Extension( - KHR_MATERIALS_IRIDESCENCE, { - Material: ExtensionDescriptor(KhrMaterialsIridescence.fromMap) -}); + KHR_MATERIALS_IRIDESCENCE, + { + Material: ExtensionDescriptor(KhrMaterialsIridescence.fromMap) + }, + validateExtensionPointer: KhrMaterialsIridescence.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_sheen/khr_materials_sheen.dart b/lib/src/ext/KHR_materials_sheen/khr_materials_sheen.dart index 21700e18..11110063 100644 --- a/lib/src/ext/KHR_materials_sheen/khr_materials_sheen.dart +++ b/lib/src/ext/KHR_materials_sheen/khr_materials_sheen.dart @@ -81,9 +81,58 @@ class KhrMaterialsSheen extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_SHEEN) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_SHEEN)) { + return PointerValidity.invalid; + } + final sheenExt = mat.extensions[KHR_MATERIALS_SHEEN] as KhrMaterialsSheen; + final subProp = pointer[inPropDepth + 2]; + TextureInfo texInfoCheckTransform; + switch (subProp) { + case SHEEN_COLOR_FACTOR: + if (sheenExt.sheenColorFactor == null || + sheenExt.sheenColorFactor.length != 3) { + return PointerValidity.invalid; + } + return PointerValidity.validIfVec3; + case SHEEN_ROUGHNESS_FACTOR: + return PointerValidity.validIfScalar; + case SHEEN_COLOR_TEXTURE: + if (sheenExt.sheenColorTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = sheenExt.sheenColorTexture; + break; + case SHEEN_ROUGHNESS_TEXTURE: + if (sheenExt.sheenRoughnessTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = sheenExt.sheenRoughnessTexture; + break; + } + if (texInfoCheckTransform != null) { + // KHR_materials_sheen can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, texInfoCheckTransform, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsSheenExtension = Extension( - KHR_MATERIALS_SHEEN, { - Material: ExtensionDescriptor(KhrMaterialsSheen.fromMap) -}); + KHR_MATERIALS_SHEEN, + { + Material: ExtensionDescriptor(KhrMaterialsSheen.fromMap) + }, + validateExtensionPointer: KhrMaterialsSheen.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_specular/khr_materials_specular.dart b/lib/src/ext/KHR_materials_specular/khr_materials_specular.dart index 10e6662f..17935cb7 100644 --- a/lib/src/ext/KHR_materials_specular/khr_materials_specular.dart +++ b/lib/src/ext/KHR_materials_specular/khr_materials_specular.dart @@ -84,9 +84,59 @@ class KhrMaterialsSpecular extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_SPECULAR) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_SPECULAR)) { + return PointerValidity.invalid; + } + final specularExt = + mat.extensions[KHR_MATERIALS_SPECULAR] as KhrMaterialsSpecular; + final subProp = pointer[inPropDepth + 2]; + TextureInfo texInfoCheckTransform; + switch (subProp) { + case SPECULAR_COLOR_FACTOR: + if (specularExt.specularColorFactor == null || + specularExt.specularColorFactor.length != 3) { + return PointerValidity.invalid; + } + return PointerValidity.validIfVec3; + case SPECULAR_FACTOR: + return PointerValidity.validIfScalar; + case SPECULAR_COLOR_TEXTURE: + if (specularExt.specularColorTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = specularExt.specularColorTexture; + break; + case SPECULAR_TEXTURE: + if (specularExt.specularTexture == null) { + return PointerValidity.invalid; + } + texInfoCheckTransform = specularExt.specularTexture; + break; + } + if (texInfoCheckTransform != null) { + // KHR_materials_specular can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, texInfoCheckTransform, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsSpecularExtension = Extension( - KHR_MATERIALS_SPECULAR, { - Material: ExtensionDescriptor(KhrMaterialsSpecular.fromMap) -}); + KHR_MATERIALS_SPECULAR, + { + Material: ExtensionDescriptor(KhrMaterialsSpecular.fromMap) + }, + validateExtensionPointer: KhrMaterialsSpecular.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_transmission/khr_materials_transmission.dart b/lib/src/ext/KHR_materials_transmission/khr_materials_transmission.dart index 7b5bb92f..c4dafe82 100644 --- a/lib/src/ext/KHR_materials_transmission/khr_materials_transmission.dart +++ b/lib/src/ext/KHR_materials_transmission/khr_materials_transmission.dart @@ -55,9 +55,43 @@ class KhrMaterialsTransmission extends GltfProperty { context.path.removeLast(); } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_TRANSMISSION) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_TRANSMISSION)) { + return PointerValidity.invalid; + } + final transmissionExt = + mat.extensions[KHR_MATERIALS_TRANSMISSION] as KhrMaterialsTransmission; + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case TRANSMISSION_FACTOR: + return PointerValidity.validIfScalar; + case TRANSMISSION_TEXTURE: + if (transmissionExt.transmissionTexture == null) { + return PointerValidity.invalid; + } + // KHR_materials_transmission can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer(context, pointer, + transmissionExt.transmissionTexture, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsTransmissionExtension = Extension( - KHR_MATERIALS_TRANSMISSION, { - Material: ExtensionDescriptor(KhrMaterialsTransmission.fromMap) -}); + KHR_MATERIALS_TRANSMISSION, + { + Material: ExtensionDescriptor(KhrMaterialsTransmission.fromMap) + }, + validateExtensionPointer: + KhrMaterialsTransmission.validateExtensionPointer); diff --git a/lib/src/ext/KHR_materials_volume/khr_materials_volume.dart b/lib/src/ext/KHR_materials_volume/khr_materials_volume.dart index ba7658d1..c931de60 100644 --- a/lib/src/ext/KHR_materials_volume/khr_materials_volume.dart +++ b/lib/src/ext/KHR_materials_volume/khr_materials_volume.dart @@ -95,9 +95,49 @@ class KhrMaterialsVolume extends GltfProperty { } } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_MATERIALS_VOLUME) { + return PointerValidity.unknown; + } + final mat = prop as Material; + if (mat == null) { + return PointerValidity.invalid; + } + if (mat.extensions == null || + !mat.extensions.containsKey(KHR_MATERIALS_VOLUME)) { + return PointerValidity.invalid; + } + final volumeExt = + mat.extensions[KHR_MATERIALS_VOLUME] as KhrMaterialsVolume; + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case THICKNESS_FACTOR: + case ATTENUATION_DISTANCE: + return PointerValidity.validIfScalar; + case ATTENUATION_COLOR: + if (volumeExt.attenuationColor == null || + volumeExt.attenuationColor.length != 3) { + return PointerValidity.invalid; + } + return PointerValidity.validIfVec3; + case THICKNESS_TEXTURE: + if (volumeExt.thicknessTexture == null) { + return PointerValidity.invalid; + } + // KHR_materials_volume can be used with KHR_texture_transform. + return KhrTextureTransform.validateExtensionPointer( + context, pointer, volumeExt.thicknessTexture, inPropDepth + 3); + } + return PointerValidity.unknown; + } } const Extension khrMaterialsVolumeExtension = Extension( - KHR_MATERIALS_VOLUME, { - Material: ExtensionDescriptor(KhrMaterialsVolume.fromMap) -}); + KHR_MATERIALS_VOLUME, + { + Material: ExtensionDescriptor(KhrMaterialsVolume.fromMap) + }, + validateExtensionPointer: KhrMaterialsVolume.validateExtensionPointer); diff --git a/lib/src/ext/KHR_node_visibility/khr_node_visibility.dart b/lib/src/ext/KHR_node_visibility/khr_node_visibility.dart index ea886c2e..cd53acd4 100644 --- a/lib/src/ext/KHR_node_visibility/khr_node_visibility.dart +++ b/lib/src/ext/KHR_node_visibility/khr_node_visibility.dart @@ -26,9 +26,37 @@ class KhrNodeVisibility extends GltfProperty { getExtensions(map, KhrNodeVisibility, context), getExtras(map, context)); } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_NODE_VISIBILITY) { + return PointerValidity.unknown; + } + final node = prop as Node; + if (node == null) { + return PointerValidity.invalid; + } + if (node.extensions == null || + !node.extensions.containsKey(KHR_NODE_VISIBILITY)) { + return PointerValidity.invalid; + } + final subProp = pointer[inPropDepth + 2]; + switch (subProp) { + case VISIBLE: + return PointerValidity.validIfBool; + case EXTENSIONS: + case EXTRAS: + return PointerValidity.unknown; + default: + return PointerValidity.invalid; + } + } } -const Extension khrNodeVisibilityExtension = - Extension(KHR_NODE_VISIBILITY, { - Node: ExtensionDescriptor(KhrNodeVisibility.fromMap), -}); +const Extension khrNodeVisibilityExtension = Extension( + KHR_NODE_VISIBILITY, + { + Node: ExtensionDescriptor(KhrNodeVisibility.fromMap), + }, + validateExtensionPointer: KhrNodeVisibility.validateExtensionPointer); diff --git a/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart b/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart index 9129fa9e..57810ba1 100644 --- a/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart +++ b/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart @@ -67,11 +67,38 @@ class KhrTextureTransform extends GltfProperty { } } } + + static PointerValidity validateExtensionPointer(Context context, + List pointer, GltfProperty prop, int inPropDepth) { + if (pointer[inPropDepth] != EXTENSIONS || + pointer[inPropDepth + 1] != KHR_TEXTURE_TRANSFORM) { + return PointerValidity.unknown; + } + final texInfo = prop as TextureInfo; + if (texInfo == null) { + return PointerValidity.invalid; + } + if (texInfo.extensions == null || + !texInfo.extensions.containsKey(KHR_TEXTURE_TRANSFORM)) { + return PointerValidity.invalid; + } + final texSubProp = pointer[inPropDepth + 2]; + switch (texSubProp) { + case OFFSET: + case SCALE: + return PointerValidity.validIfVec2; + case ROTATION: + return PointerValidity.validIfScalar; + } + return PointerValidity.unknown; + } } -const Extension khrTextureTransformExtension = - Extension(KHR_TEXTURE_TRANSFORM, { - TextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), - NormalTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), - OcclusionTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), -}); +const Extension khrTextureTransformExtension = Extension( + KHR_TEXTURE_TRANSFORM, + { + TextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), + NormalTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), + OcclusionTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), + }, + validateExtensionPointer: KhrTextureTransform.validateExtensionPointer); diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index 3e4368e5..e315c81e 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -62,11 +62,13 @@ export 'package:gltf/src/ext/KHR_texture_transform/khr_texture_transform.dart'; class Extension { const Extension(this.name, this.functions, - {this.init, this.required = false}); + {this.init, this.validateExtensionPointer, this.required = false}); final String name; final Map functions; final void Function(Context) init; + final PointerValidity Function(Context, List, GltfProperty, int) + validateExtensionPointer; final bool required; } diff --git a/test/base/data/animation/empty_objects.gltf.report.json b/test/base/data/animation/empty_objects.gltf.report.json index e80510f4..e69f6621 100644 --- a/test/base/data/animation/empty_objects.gltf.report.json +++ b/test/base/data/animation/empty_objects.gltf.report.json @@ -34,27 +34,27 @@ }, { "code": "UNDEFINED_PROPERTY", - "message": "Property 'sampler' must be defined.", + "message": "Property 'target' must be defined.", "severity": 0, "pointer": "/animations/2/channels/0" }, { "code": "UNDEFINED_PROPERTY", - "message": "Property 'target' must be defined.", + "message": "Property 'sampler' must be defined.", "severity": 0, "pointer": "/animations/2/channels/0" }, { "code": "UNDEFINED_PROPERTY", - "message": "Property 'sampler' must be defined.", + "message": "Property 'path' must be defined.", "severity": 0, - "pointer": "/animations/2/channels/1" + "pointer": "/animations/2/channels/1/target" }, { "code": "UNDEFINED_PROPERTY", - "message": "Property 'path' must be defined.", + "message": "Property 'sampler' must be defined.", "severity": 0, - "pointer": "/animations/2/channels/1/target" + "pointer": "/animations/2/channels/1" }, { "code": "UNDEFINED_PROPERTY", @@ -92,4 +92,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/assets.json b/test/ext/KHR_animation_pointer/assets.json index 87f7e7a4..ed7b954f 100644 --- a/test/ext/KHR_animation_pointer/assets.json +++ b/test/ext/KHR_animation_pointer/assets.json @@ -7,9 +7,14 @@ "invalid_pointer_syntax.gltf": "Invalid pointer syntax", "missing_extension_declaration.gltf": "Missing extension declaration", "missing_pointer.gltf": "Missing pointer string", + "pointer_to_extras.gltf": "Pointer to extras", + "pointer_to_invalid_material.gltf": "Pointer to invalid material", + "pointer_to_light.gltf": "One valid pointer to light property, one invalid pointer", + "pointer_to_sheen_material.gltf": "One valid pointer to sheen material property, one invalid pointer", + "pointer_to_unknown_extension.gltf": "Pointer to unknown extension", "custom_property.gltf": "Custom property", "unexpected_extension.gltf": "Unexpected extension object location", "valid.gltf": "Valid" } } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf index 8196ef61..0eae5712 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf @@ -48,4 +48,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf.report.json index 72d9b8dc..4c721dfc 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/custom_property.gltf.report.json @@ -5,7 +5,7 @@ "issues": { "numErrors": 0, "numWarnings": 1, - "numInfos": 2, + "numInfos": 1, "numHints": 0, "messages": [ { @@ -14,12 +14,6 @@ "severity": 1, "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer/customProperty" }, - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, { "code": "UNUSED_OBJECT", "message": "This object may be unused.", @@ -47,4 +41,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_node.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_node.gltf.report.json index 31d422db..6418d66d 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_node.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_node.gltf.report.json @@ -5,15 +5,9 @@ "issues": { "numErrors": 1, "numWarnings": 0, - "numInfos": 2, + "numInfos": 1, "numHints": 0, "messages": [ - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, { "code": "KHR_ANIMATION_POINTER_ANIMATION_CHANNEL_TARGET_NODE", "message": "This extension requires the animation channel target node to be undefined.", @@ -47,4 +41,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_path.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_path.gltf.report.json index 3e37d219..e293e777 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_path.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_channel_target_path.gltf.report.json @@ -5,7 +5,7 @@ "issues": { "numErrors": 1, "numWarnings": 1, - "numInfos": 2, + "numInfos": 1, "numHints": 0, "messages": [ { @@ -14,12 +14,6 @@ "severity": 1, "pointer": "/animations/0/channels/0/target/path" }, - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, { "code": "KHR_ANIMATION_POINTER_ANIMATION_CHANNEL_TARGET_PATH", "message": "This extension requires the animation channel target path to be 'pointer'. Found 'path' instead.", @@ -53,4 +47,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_pointer_syntax.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_pointer_syntax.gltf.report.json index 423be9af..891d2179 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_pointer_syntax.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/invalid_pointer_syntax.gltf.report.json @@ -5,7 +5,7 @@ "issues": { "numErrors": 1, "numWarnings": 0, - "numInfos": 2, + "numInfos": 1, "numHints": 0, "messages": [ { @@ -14,12 +14,6 @@ "severity": 0, "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer/pointer" }, - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, { "code": "UNUSED_OBJECT", "message": "This object may be unused.", @@ -47,4 +41,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/missing_pointer.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/missing_pointer.gltf.report.json index 0b8bf63d..2cc0fe83 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/missing_pointer.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/missing_pointer.gltf.report.json @@ -5,7 +5,7 @@ "issues": { "numErrors": 1, "numWarnings": 0, - "numInfos": 1, + "numInfos": 0, "numHints": 0, "messages": [ { @@ -13,12 +13,6 @@ "message": "Property 'pointer' must be defined.", "severity": 0, "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" } ], "truncated": false @@ -41,4 +35,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf new file mode 100644 index 00000000..9f60bcc6 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf @@ -0,0 +1,48 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_animation_pointer" + ], + "extras": { + "someExtra": 42 + }, + "accessors": [ + { + "count": 2, + "type": "SCALAR", + "componentType": 5126, + "min": [ 0 ], + "max": [ 1 ] + }, + { + "count": 2, + "type": "VEC4", + "componentType": 5126 + } + ], + "animations": [ + { + "channels": [ + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/extras/someExtra" + } + } + }, + "sampler": 0 + } + ], + "samplers": [ + { + "input": 0, + "output": 1 + } + ] + } + ] +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf.report.json new file mode 100644 index 00000000..56997ac3 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf.report.json @@ -0,0 +1,35 @@ +{ + "uri": "test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_extras.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 0, + "numWarnings": 0, + "numInfos": 0, + "numHints": 1, + "messages": [ + { + "code": "KHR_ANIMATION_POINTER_CANNOT_VALIDATE_EXTRAS", + "message": "Cannot validate pointers into non-standardized extras.", + "severity": 3, + "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": ["KHR_animation_pointer"], + "animationCount": 1, + "materialCount": 0, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": false, + "hasDefaultScene": false, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf new file mode 100644 index 00000000..e4cffca2 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf @@ -0,0 +1,50 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_animation_pointer" + ], + "materials": [ + { + "pbrMetallicRoughness": { } + } + ], + "accessors": [ + { + "count": 2, + "type": "SCALAR", + "componentType": 5126, + "min": [ 0 ], + "max": [ 1 ] + }, + { + "count": 2, + "type": "VEC4", + "componentType": 5126 + } + ], + "animations": [ + { + "channels": [ + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/materials/1/pbrMetallicRoughness/baseColorFactor" + } + } + }, + "sampler": 0 + } + ], + "samplers": [ + { + "input": 0, + "output": 1 + } + ] + } + ] +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf.report.json new file mode 100644 index 00000000..384201a5 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf.report.json @@ -0,0 +1,41 @@ +{ + "uri": "test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_invalid_material.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 1, + "numWarnings": 0, + "numInfos": 1, + "numHints": 0, + "messages": [ + { + "code": "KHR_ANIMATION_POINTER_INVALID_POINTER", + "message": "The pointer '/materials/1/pbrMetallicRoughness/baseColorFactor' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.", + "severity": 0, + "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" + }, + { + "code": "UNUSED_OBJECT", + "message": "This object may be unused.", + "severity": 2, + "pointer": "/materials/0" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": ["KHR_animation_pointer"], + "animationCount": 1, + "materialCount": 1, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": false, + "hasDefaultScene": false, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf new file mode 100644 index 00000000..bc25f51a --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf @@ -0,0 +1,78 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_animation_pointer", + "KHR_lights_punctual" + ], + "extensions": { + "KHR_lights_punctual": { + "lights": [ + { + "range": 5.0, + "type": "point" + } + ] + } + }, + "accessors": [ + { + "count": 2, + "type": "SCALAR", + "componentType": 5126, + "min": [ 0 ], + "max": [ 1 ] + }, + { + "count": 2, + "type": "SCALAR", + "componentType": 5126 + } + ], + "animations": [ + { + "channels": [ + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/extensions/KHR_lights_punctual/lights/0/intensity" + } + } + }, + "sampler": 0 + }, + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/extensions/KHR_lights_punctual/lights/0/nonexistentProperty" + } + } + }, + "sampler": 0 + } + ], + "samplers": [ + { + "input": 0, + "output": 1 + } + ] + } + ], + "nodes": [ + { + "extensions": { + "KHR_lights_punctual": { + "light": 0 + } + } + } + ], + "scene": 0, + "scenes": [{"nodes": [0]}] +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf.report.json new file mode 100644 index 00000000..5e46de48 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf.report.json @@ -0,0 +1,35 @@ +{ + "uri": "test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_light.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 0, + "numWarnings": 0, + "numInfos": 1, + "numHints": 0, + "messages": [ + { + "code": "INCOMPLETE_EXTENSION_SUPPORT", + "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", + "severity": 2, + "pointer": "/animations/0/channels/1/target/extensions/KHR_animation_pointer" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": ["KHR_animation_pointer", "KHR_lights_punctual"], + "animationCount": 1, + "materialCount": 0, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": false, + "hasDefaultScene": true, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf new file mode 100644 index 00000000..bc35d9f2 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf @@ -0,0 +1,80 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_animation_pointer", + "KHR_materials_sheen", + "KHR_texture_transform" + ], + "materials": [ + { + "extensions": { + "KHR_materials_sheen": { + "sheenColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ 0.1, 0.2 ] + } + } + } + } + }, + "pbrMetallicRoughness": { } + } + ], + "accessors": [ + { + "count": 2, + "type": "SCALAR", + "componentType": 5126, + "min": [ 0 ], + "max": [ 1 ] + }, + { + "count": 2, + "type": "VEC2", + "componentType": 5126 + } + ], + "animations": [ + { + "channels": [ + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/materials/0/extensions/KHR_materials_sheen/sheenColorTexture/extensions/KHR_texture_transform/offset" + } + } + }, + "sampler": 0 + }, + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/materials/0/extensions/KHR_materials_sheen/sheenRoughnessTexture/extensions/KHR_texture_transform/offset" + } + } + }, + "sampler": 0 + } + ], + "samplers": [ + { + "input": 0, + "output": 1 + } + ] + } + ], + "textures": [ + { + + } + ] +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf.report.json new file mode 100644 index 00000000..97c9934c --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf.report.json @@ -0,0 +1,45 @@ +{ + "uri": "test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_sheen_material.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 1, + "numWarnings": 0, + "numInfos": 1, + "numHints": 0, + "messages": [ + { + "code": "KHR_ANIMATION_POINTER_INVALID_POINTER", + "message": "The pointer '/materials/0/extensions/KHR_materials_sheen/sheenRoughnessTexture/extensions/KHR_texture_transform/offset' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.", + "severity": 0, + "pointer": "/animations/0/channels/1/target/extensions/KHR_animation_pointer" + }, + { + "code": "UNUSED_OBJECT", + "message": "This object may be unused.", + "severity": 2, + "pointer": "/materials/0" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": [ + "KHR_animation_pointer", + "KHR_materials_sheen", + "KHR_texture_transform" + ], + "animationCount": 1, + "materialCount": 1, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": true, + "hasDefaultScene": false, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf new file mode 100644 index 00000000..c1e80305 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf @@ -0,0 +1,50 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_animation_pointer" + ], + "materials": [ + { + "pbrMetallicRoughness": { } + } + ], + "accessors": [ + { + "count": 2, + "type": "SCALAR", + "componentType": 5126, + "min": [ 0 ], + "max": [ 1 ] + }, + { + "count": 2, + "type": "VEC4", + "componentType": 5126 + } + ], + "animations": [ + { + "channels": [ + { + "target": { + "path": "pointer", + "extensions": { + "KHR_animation_pointer": { + "pointer": "/materials/0/extensions/UNKNOWN_extension/someProperty" + } + } + }, + "sampler": 0 + } + ], + "samplers": [ + { + "input": 0, + "output": 1 + } + ] + } + ] +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf.report.json new file mode 100644 index 00000000..0246ce31 --- /dev/null +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf.report.json @@ -0,0 +1,41 @@ +{ + "uri": "test/ext/KHR_animation_pointer/data/animation/channel/target/pointer_to_unknown_extension.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 0, + "numWarnings": 0, + "numInfos": 2, + "numHints": 0, + "messages": [ + { + "code": "INCOMPLETE_EXTENSION_SUPPORT", + "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", + "severity": 2, + "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" + }, + { + "code": "UNUSED_OBJECT", + "message": "This object may be unused.", + "severity": 2, + "pointer": "/materials/0" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": ["KHR_animation_pointer"], + "animationCount": 1, + "materialCount": 1, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": false, + "hasDefaultScene": false, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf b/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf index 0aee94f0..0bb322e2 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf @@ -67,4 +67,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf.report.json b/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf.report.json index 4771ab4d..3c0a375b 100644 --- a/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf.report.json +++ b/test/ext/KHR_animation_pointer/data/animation/channel/target/valid.gltf.report.json @@ -5,21 +5,9 @@ "issues": { "numErrors": 0, "numWarnings": 0, - "numInfos": 3, + "numInfos": 1, "numHints": 0, "messages": [ - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/0/target/extensions/KHR_animation_pointer" - }, - { - "code": "INCOMPLETE_EXTENSION_SUPPORT", - "message": "Validation support for this extension is incomplete; the asset may have undetected issues.", - "severity": 2, - "pointer": "/animations/0/channels/1/target/extensions/KHR_animation_pointer" - }, { "code": "UNUSED_OBJECT", "message": "This object may be unused.", @@ -47,4 +35,4 @@ "maxInfluences": 0, "maxAttributes": 0 } -} \ No newline at end of file +} diff --git a/test/ext/KHR_node_visibility/assets.json b/test/ext/KHR_node_visibility/assets.json index f39e6631..4e8e305b 100644 --- a/test/ext/KHR_node_visibility/assets.json +++ b/test/ext/KHR_node_visibility/assets.json @@ -2,6 +2,7 @@ "node": { "name": "node.KHR_node_visibility", "tests": { + "animation_pointer.gltf": "Animation pointer", "custom_property.gltf": "Custom property", "unexpected_extension.gltf": "Unexpected extension object location", "valid.gltf": "Valid" diff --git a/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf b/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf new file mode 100644 index 00000000..e53398d7 --- /dev/null +++ b/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf @@ -0,0 +1,35 @@ +{ + "accessors": [ + { "componentType": 5126, "count": 11, "max": [5.0], "min": [0.0], "normalized": false, "type": "SCALAR" }, + { "componentType": 5121, "count": 11, "max": [1], "min": [0], "normalized": false, "type": "SCALAR" } + ], + "animations": [ + { + "channels": [{ "sampler": 0, "target": { "extensions": { "KHR_animation_pointer": { "pointer": "/nodes/0/extensions/KHR_node_visibility/visible" } }, + "path": "pointer" } }], + "samplers": [{ "input": 0, "interpolation": "STEP", "output": 1 }] + }, + { + "channels": [{ "sampler": 0, "target": { "extensions": { "KHR_animation_pointer": { "pointer": "/nodes/1/extensions/KHR_node_visibility/visible" } }, + "path": "pointer" } }], + "samplers": [{ "input": 0, "interpolation": "STEP", "output": 1 }] + }, + { + "channels": [{ "sampler": 0, "target": { "extensions": { "KHR_animation_pointer": { "pointer": "/nodes/2/extensions/KHR_node_visibility/visible" } }, + "path": "pointer" } }], + "samplers": [{ "input": 0, "interpolation": "STEP", "output": 1 }] + }, + { + "channels": [{ "sampler": 0, "target": { "extensions": { "KHR_animation_pointer": { "pointer": "/nodes/0/extensions/KHR_node_visibility/invalidProperty" } }, + "path": "pointer" } }], + "samplers": [{ "input": 0, "interpolation": "STEP", "output": 1 }] + } + ], + "asset": { "copyright": "2025 The Khronos Group", "version": "2.0" }, + "extensionsRequired": ["KHR_node_visibility"], + "extensionsUsed": ["KHR_animation_pointer", "KHR_node_visibility"], + "nodes": [ + { "extensions": { "KHR_node_visibility": { "visible": false } }, "name": "AnimatedVisibility" }, + { "name": "LackingVisibilityExtension" } + ] +} diff --git a/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf.report.json b/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf.report.json new file mode 100644 index 00000000..8fe5dcfa --- /dev/null +++ b/test/ext/KHR_node_visibility/data/node/animation_pointer.gltf.report.json @@ -0,0 +1,66 @@ +{ + "uri": "test/ext/KHR_node_visibility/data/node/animation_pointer.gltf", + "mimeType": "model/gltf+json", + "issues": { + "numErrors": 3, + "numWarnings": 0, + "numInfos": 3, + "numHints": 0, + "messages": [ + { + "code": "KHR_ANIMATION_POINTER_INVALID_POINTER", + "message": "The pointer '/nodes/1/extensions/KHR_node_visibility/visible' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.", + "severity": 0, + "pointer": "/animations/1/channels/0/target/extensions/KHR_animation_pointer" + }, + { + "code": "KHR_ANIMATION_POINTER_INVALID_POINTER", + "message": "The pointer '/nodes/2/extensions/KHR_node_visibility/visible' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.", + "severity": 0, + "pointer": "/animations/2/channels/0/target/extensions/KHR_animation_pointer" + }, + { + "code": "KHR_ANIMATION_POINTER_INVALID_POINTER", + "message": "The pointer '/nodes/0/extensions/KHR_node_visibility/invalidProperty' does not point to a valid property in the glTF JSON. Animated properties must be defined in the JSON document.", + "severity": 0, + "pointer": "/animations/3/channels/0/target/extensions/KHR_animation_pointer" + }, + { + "code": "NODE_EMPTY", + "message": "Empty node encountered.", + "severity": 2, + "pointer": "/nodes/1" + }, + { + "code": "UNUSED_OBJECT", + "message": "This object may be unused.", + "severity": 2, + "pointer": "/nodes/0" + }, + { + "code": "UNUSED_OBJECT", + "message": "This object may be unused.", + "severity": 2, + "pointer": "/nodes/1" + } + ], + "truncated": false + }, + "info": { + "version": "2.0", + "extensionsUsed": ["KHR_animation_pointer", "KHR_node_visibility"], + "extensionsRequired": ["KHR_node_visibility"], + "animationCount": 4, + "materialCount": 0, + "hasMorphTargets": false, + "hasSkins": false, + "hasTextures": false, + "hasDefaultScene": false, + "drawCallCount": 0, + "totalVertexCount": 0, + "totalTriangleCount": 0, + "maxUVs": 0, + "maxInfluences": 0, + "maxAttributes": 0 + } +}