Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ The following options will cause the validator to _exclude_ tileset files (i.e.
"excludeContentTypes": [ "CONTENT_TYPE_TILESET" ]
}
```
The following options will cause the validator to validate that each bounding volume contains all points and vertices of the content of the respective tile and all its descendants:
```JSON
{
"validateBoundingVolumeContainment": true
}
```
Note that this form of validation may be computationally expensive, and is therefore disabled by default.

The options can also be part of a configuration file, as described in the next section.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"3d-tiles-tools": "0.5.0",
"3d-tiles-tools": "0.5.3",
"cesium": "^1.97.0",
"gltf-validator": "^2.0.0-dev.3.9",
"minimatch": "^5.1.0",
Expand Down
312 changes: 312 additions & 0 deletions specs/BoundingVolumeContainmentValidationSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
import { ValidationOptions } from "../src/validation/ValidationOptions";
import { Validators } from "../src/validation/Validators";

describe("Bounding volume containment validation", function () {
//==========================================================================
// Valid basic:

it("detects no issues in basic/validWithB3dm", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithB3dm.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithB3dmRtc", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithB3dmRtc.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithCmpt", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithCmpt.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithExternal", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithExternal.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbBasic", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbBasic.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbContentBox", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbContentBox.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbNested", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbNested.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbRegion", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbRegion.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbRotation", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbRotation.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbScale", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbScale.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbSphereTRS", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbSphereTRS.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithGlbTranslation", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithGlbTranslation.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithI3dm", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithI3dm.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithPnts", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithPnts.json",
validationOptions
);
expect(result.length).toEqual(0);
});

it("detects no issues in basic/validWithPntsRtc", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/validWithPntsRtc.json",
validationOptions
);
expect(result.length).toEqual(0);
});

//==========================================================================
// Invalid basic:

it("detects issues in basic/withExternalInvalidBox", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withExternalInvalidBox.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual("EXTERNAL_TILESET_VALIDATION_ERROR");
});

it("detects issues in basic/withGlbInvalidContentBox", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbInvalidContentBox.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

it("detects issues in basic/withGlbInvalidContentBox", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbInvalidContentBox.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

it("detects issues in basic/withGlbInvalidRegion", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbInvalidRegion.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

it("detects issues in basic/withGlbNestedInvalidBoxInner", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbNestedInvalidBoxInner.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

it("detects issues in basic/withGlbNestedInvalidBoxRoot", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbNestedInvalidBoxRoot.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

it("detects issues in basic/withGlbTRSInvalidSphere", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/basic/withGlbTRSInvalidSphere.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});

//==========================================================================
// Valid ImplicitOctree:

// Omitted - see https://github.com/CesiumGS/cesium/issues/13195#issuecomment-3915058716
xit("detects no issues in ImplicitOctree/tilesetWithMetadataBoundingVolume", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/ImplicitOctree/tilesetWithMetadataBoundingVolume.json",
validationOptions
);
expect(result.length).toEqual(0);
});

//==========================================================================
// Invalid ImplicitOctree:

it("detects issues in ImplicitOctree/tilesetWithInvalidBoundingVolume", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/ImplicitOctree/tilesetWithInvalidBoundingVolume.json",
validationOptions
);

// There are 14 errors, because the whole hierarchy is invalid when
// the root bounding volume is invalid:
// '/root' for content 'content/content_0__0_0_0.glb'
// '/root' for content 'content/content_1__0_0_1.glb'
// '/root' for content 'content/content_2__0_3_3.glb'
// '/root' for content 'content/content_3__7_7_7.glb'
// '/root/at[0][0,0,0]' for content 'content/content_0__0_0_0.glb'
// '/root/at[0][0,0,0]' for content 'content/content_3__7_7_7.glb'
// '/root/at[0][0,0,0]' for content 'content/content_2__0_3_3.glb'
// '/root/at[0][0,0,0]' for content 'content/content_1__0_0_1.glb'
// '/root/at[1][0,0,1]' for content 'content/content_1__0_0_1.glb'
// '/root/at[1][0,1,1]' for content 'content/content_2__0_3_3.glb'
// '/root/at[1][1,1,1]' for content 'content/content_3__7_7_7.glb'
// '/root/at[2][0,3,3]' for content 'content/content_2__0_3_3.glb'
// '/root/at[2][3,3,3]' for content 'content/content_3__7_7_7.glb'
// '/root/at[3][7,7,7]' for content 'content/content_3__7_7_7.glb'
expect(result.length).toEqual(14);
for (let i = 0; i < 14; i++) {
expect(result.get(i).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
}
});

// Omitted - see https://github.com/CesiumGS/cesium/issues/13195#issuecomment-3915058716
xit("detects issues in ImplicitOctree/tilesetWithInvalidMetadataBoundingVolume", async function () {
const validationOptions = new ValidationOptions();
validationOptions.validateBoundingVolumeContainment = true;
const result = await Validators.validateTilesetFile(
"specs/data/boundingVolumes/ImplicitOctree/tilesetWithInvalidMetadataBoundingVolume.json",
validationOptions
);
expect(result.length).toEqual(1);
expect(result.get(0).type).toEqual(
"CONTENT_NOT_ENCLOSED_BY_BOUNDING_VOLUME"
);
});
});
7 changes: 7 additions & 0 deletions specs/TilesetValidationSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,13 @@ describe("Tileset validation", function () {
expect(result.length).toEqual(0);
});

it("detects no issues in validTilesetWithValidGlb", async function () {
const result = await Validators.validateTilesetFile(
"specs/data/tilesets/validTilesetWithValidGlb.json"
);
expect(result.length).toEqual(0);
});

it("detects no issues in validTilesetWithValidSchemaFromUri", async function () {
const result = await Validators.validateTilesetFile(
"specs/data/tilesets/validTilesetWithValidSchemaFromUri.json"
Expand Down
8 changes: 8 additions & 0 deletions specs/data/boundingVolumes/ImplicitOctree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

Test data for the bounding volume containment validation.

This is a simple implicit octree that contains GLB files in
the "lower half" (z=0 to z=0.5) of the octree, which
requires the metadata `TILE_BOUNDING_BOX` to be defined as
`[ 0.5, -0.5, 0.25, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.25 ]`
to be valid.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"asset" : {
"extras" : {
"generator" : "J3DTiles-0.0.1-SNAPSHOT"
},
"version" : "1.1"
},
"geometricError" : 1048576.0,
"root" : {
"boundingVolume" : {
"box" : [ 0.6, -0.6, 0.6, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5 ]
},
"geometricError" : 1024.0,
"refine" : "REPLACE",
"content" : {
"uri" : "content/content_{level}__{x}_{y}_{z}.glb"
},
"implicitTiling" : {
"subdivisionScheme" : "OCTREE",
"subtreeLevels" : 2,
"availableLevels" : 4,
"subtrees" : {
"uri" : "subtrees/{level}.{x}.{y}.{z}.subtree"
}
}
}
}
Loading
Loading