Skip to content

Commit

Permalink
First pass for EXT_structural_metadata validation
Browse files Browse the repository at this point in the history
  • Loading branch information
javagl committed Sep 26, 2023
1 parent e427da1 commit 00ee092
Show file tree
Hide file tree
Showing 13 changed files with 1,024 additions and 238 deletions.
16 changes: 16 additions & 0 deletions src/issues/GltfExtensionValidationIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,20 @@ export class GltfExtensionValidationIssues {
const issue = new ValidationIssue(type, path, message, severity);
return issue;
}

/**
* Indicates that a certain property was defined in a context where
* this type is not allowed. For example, when a variable-length
* array or a 'STRING' property are used in a property texture.
*
* @param path - The path for the `ValidationIssue`
* @param message - The message for the `ValidationIssue`
* @returns The `ValidationIssue`
*/
static INVALID_METADATA_PROPERTY_TYPE(path: string, message: string) {
const type = "INVALID_METADATA_PROPERTY_TYPE";
const severity = ValidationIssueSeverity.ERROR;
const issue = new ValidationIssue(type, path, message, severity);
return issue;
}
}
121 changes: 36 additions & 85 deletions src/validation/SubtreeValidator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { defined } from "3d-tiles-tools";
import { defaultValue } from "3d-tiles-tools";
import { Schema } from "3d-tiles-tools";
import { Buffers } from "3d-tiles-tools";

import { ResourceResolver } from "3d-tiles-tools";

import { Schema } from "3d-tiles-tools";
import { Subtree } from "3d-tiles-tools";
import { Availability } from "3d-tiles-tools";
import { TileImplicitTiling } from "3d-tiles-tools";

import { MetadataUtilities } from "3d-tiles-tools";
import { BinaryBufferStructure } from "3d-tiles-tools";
import { BinarySubtreeData } from "3d-tiles-tools";
import { BinarySubtreeDataResolver } from "3d-tiles-tools";
import { BinaryPropertyTable } from "3d-tiles-tools";

import { Validator } from "./Validator";
import { ValidationContext } from "./ValidationContext";
import { BasicValidator } from "./BasicValidator";
Expand All @@ -13,28 +22,17 @@ import { SubtreeConsistencyValidator } from "./SubtreeConsistencyValidator";
import { SubtreeInfoValidator } from "./SubtreeInfoValidator";
import { RootPropertyValidator } from "./RootPropertyValidator";
import { ExtendedObjectsValidators } from "./ExtendedObjectsValidators";
import { ValidatedElement } from "./ValidatedElement";

import { BinaryBufferStructureValidator } from "./BinaryBufferStructureValidator";

import { BinaryPropertyTable } from "3d-tiles-tools";

import { MetadataEntityValidator } from "./metadata/MetadataEntityValidator";
import { PropertyTableValidator } from "./metadata/PropertyTableValidator";
import { MetadataUtilities } from "3d-tiles-tools";
import { BinaryBufferStructure } from "3d-tiles-tools";

import { BinarySubtreeData } from "3d-tiles-tools";
import { BinarySubtreeDataResolver } from "3d-tiles-tools";

import { Subtree } from "3d-tiles-tools";
import { Availability } from "3d-tiles-tools";
import { TileImplicitTiling } from "3d-tiles-tools";
import { PropertyTablesDefinitionValidator } from "./metadata/PropertyTablesDefinitionValidator";

import { JsonValidationIssues } from "../issues/JsonValidationIssues";
import { IoValidationIssues } from "../issues/IoValidationIssue";
import { StructureValidationIssues } from "../issues/StructureValidationIssues";
import { BinaryPropertyTableValidator } from "./metadata/BinaryPropertyTableValidator";
import { ValidatedElement } from "./ValidatedElement";

/**
* A class for validations related to `subtree` objects that have
Expand Down Expand Up @@ -660,71 +658,24 @@ export class SubtreeValidator implements Validator<Buffer> {
): boolean {
let result = true;

// This stores whether there has been a definition of propertyTables
// at all
let hasPropertyTablesDefinition = false;

// This are the validated property tables - i.e. this will only
// be defined if the (defined) property tables have turned out
// to be valid
let validatedPropertyTables = undefined;

// Validate the propertyTables
const propertyTables = subtree.propertyTables;
const propertyTablesPath = path + "/propertyTables";
if (defined(propertyTables)) {
hasPropertyTablesDefinition = true;
const numBufferViews = defaultValue(subtree.bufferViews?.length, 0);
const numBufferViews = defaultValue(subtree.bufferViews?.length, 0);
const propertyTablesState =
PropertyTablesDefinitionValidator.validatePropertyTablesDefinition(
path,
"subtree",
subtree.propertyTables,
numBufferViews,
this.schemaState,
context
);

if (!this.schemaState.wasPresent) {
// If there are property tables, then there MUST be a schema definition
const message =
`The subtree defines 'propertyTables' but ` +
`there was no schema definition`;
const issue = StructureValidationIssues.REQUIRED_VALUE_NOT_FOUND(
path,
message
);
context.addIssue(issue);
result = false;
} else if (defined(this.schemaState.validatedElement)) {
// The propertyTables MUST be an array of at least 1 objects
if (
!BasicValidator.validateArray(
propertyTablesPath,
"propertyTables",
propertyTables,
1,
undefined,
"object",
context
)
) {
result = false;
} else {
// Validate each propertyTable
let propertyTablesAreValid = true;
for (let i = 0; i < propertyTables.length; i++) {
const propertyTable = propertyTables[i];
const propertyTablePath = propertyTablesPath + "/" + i;
if (
!PropertyTableValidator.validatePropertyTable(
propertyTablePath,
propertyTable,
numBufferViews,
this.schemaState.validatedElement,
context
)
) {
result = false;
propertyTablesAreValid = false;
}
}
if (propertyTablesAreValid) {
validatedPropertyTables = propertyTables;
}
}
}
// When there have been property tables, but they have
// not been valid, then the overall result is invalid.
if (
propertyTablesState.wasPresent &&
!defined(propertyTablesState.validatedElement)
) {
result = false;
}

// Validate the tileMetadata
Expand Down Expand Up @@ -757,7 +708,7 @@ export class SubtreeValidator implements Validator<Buffer> {
);
context.addIssue(issue);
result = false;
} else if (!hasPropertyTablesDefinition) {
} else if (!propertyTablesState.wasPresent) {
// If there is tileMetadata, then there MUST be propertyTables
const message =
`The subtree defines 'tileMetadata' but ` +
Expand All @@ -770,7 +721,7 @@ export class SubtreeValidator implements Validator<Buffer> {
result = false;
} else if (
defined(this.schemaState.validatedElement) &&
defined(validatedPropertyTables)
defined(propertyTablesState.validatedElement)
) {
// The tileMetadata MUST be smaller than the numberOfPropertyTables
if (
Expand All @@ -780,7 +731,7 @@ export class SubtreeValidator implements Validator<Buffer> {
tileMetadata,
0,
true,
validatedPropertyTables.length,
propertyTablesState.validatedElement.length,
false,
context
)
Expand Down Expand Up @@ -820,7 +771,7 @@ export class SubtreeValidator implements Validator<Buffer> {
);
context.addIssue(issue);
result = false;
} else if (!hasPropertyTablesDefinition) {
} else if (!propertyTablesState.wasPresent) {
// If there is contentMetadata, then there MUST be propertyTables
const message =
`The subtree defines 'contentMetadata' but ` +
Expand All @@ -833,7 +784,7 @@ export class SubtreeValidator implements Validator<Buffer> {
result = false;
} else if (
defined(this.schemaState.validatedElement) &&
defined(validatedPropertyTables)
defined(propertyTablesState.validatedElement)
) {
for (let i = 0; i < contentMetadata.length; i++) {
const elementPath = contentMetadataPath + "/" + i;
Expand All @@ -848,7 +799,7 @@ export class SubtreeValidator implements Validator<Buffer> {
element,
0,
true,
validatedPropertyTables.length,
propertyTablesState.validatedElement.length,
false,
context
)
Expand Down
159 changes: 159 additions & 0 deletions src/validation/gltfExtensions/ExtStructuralMetadataValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { defined } from "3d-tiles-tools";
import { defaultValue } from "3d-tiles-tools";

import { ValidationContext } from "./../ValidationContext";
import { BasicValidator } from "./../BasicValidator";

import { GltfData } from "./GltfData";
import { PropertyTexturesDefinitionValidator } from "./PropertyTexturesDefinitionValidator";

import { SchemaDefinitionValidator } from "../metadata/SchemaDefinitionValidator";
import { PropertyTablesDefinitionValidator } from "../metadata/PropertyTablesDefinitionValidator";

/**
* A class for validating the `EXT_structural_metadata` extension in
* glTF assets.
*
* This class assumes that the structure of the glTF asset itself
* has already been validated (e.g. with the glTF Validator).
*
* @internal
*/
export class ExtStructuralMetadataValidator {
/**
* Performs the validation to ensure that the `EXT_structural_metadata`
* extensions in the given glTF are valid
*
* @param path - The path for validation issues
* @param gltfData - The glTF data, containing the parsed JSON and the
* (optional) binary buffer
* @param context - The `ValidationContext` that any issues will be added to
* @returns Whether the object was valid
*/
static async validateGltf(
path: string,
gltfData: GltfData,
context: ValidationContext
): Promise<boolean> {
const gltf = gltfData.gltf;

// Dig into the (untyped) JSON representation of the
// glTF, to find the extension objects.

const extensions = gltf.extensions;
if (!extensions) {
return true;
}
const structuralMetadata = extensions["EXT_structural_metadata"];
if (structuralMetadata) {
const structuralMetadataValid =
await ExtStructuralMetadataValidator.validateStructuralMetadata(
path,
structuralMetadata,
gltfData,
context
);
if (!structuralMetadataValid) {
return false;
}
}

return true;
}

/**
* Validate the given top-level EXT_structural_metadata extension object
* that was found in the given glTF.
*
* @param path - The path for validation issues
* @param meshFeatures - The EXT_mesh_features extension object
* @param gltfData - The glTF data
* @param context - The `ValidationContext` that any issues will be added to
* @returns Whether the object was valid
*/
private static async validateStructuralMetadata(
path: string,
structuralMetadata: any,
gltfData: GltfData,
context: ValidationContext
): Promise<boolean> {
// Make sure that the given value is an object
if (
!BasicValidator.validateObject(
path,
"structuralMetadata",
structuralMetadata,
context
)
) {
return false;
}

let result = true;

// Validate the schema definition, consisting of the
// `schema` or `schemaUri`
const schemaState =
await SchemaDefinitionValidator.validateSchemaDefinition(
path,
"structuralMetadata",
structuralMetadata.schema,
structuralMetadata.schemaUri,
context
);

// When there was a schema definition, but the schema
// itself was not valid, then the overall result
// is invalid
if (schemaState.wasPresent && !defined(schemaState.validatedElement)) {
result = false;
}

const gltf = gltfData.gltf;
const numBufferViews = defaultValue(gltf.bufferViews?.length, 0);

// Validate the property tables definition
const propertyTablesState =
PropertyTablesDefinitionValidator.validatePropertyTablesDefinition(
path,
"structural metadata extension object",
structuralMetadata.propertyTables,
numBufferViews,
schemaState,
context
);

// When there was a property tables definition, but the
// property tables are not valid, then the overall result
// is invalid
if (
propertyTablesState.wasPresent &&
!defined(propertyTablesState.validatedElement)
) {
result = false;
}

// Validate the property textures definition
const propertyTexturesState =
PropertyTexturesDefinitionValidator.validatePropertyTexturesDefinition(
path,
"structural metadata extension object",
structuralMetadata.propertyTextures,
numBufferViews,
schemaState,
context
);

// When there was a property textures definition, but the
// property textures are not valid, then the overall result
// is invalid
if (
propertyTexturesState.wasPresent &&
!defined(propertyTexturesState.validatedElement)
) {
result = false;
}

return result;
}
}
Loading

0 comments on commit 00ee092

Please sign in to comment.