text: import {
    Batched3DModel3DTileContent,
    Cartesian3,
    Cesium3DTile,
    Cesium3DTileRefine,
    Cesium3DTileset,
    Ellipsoid,
    HeadingPitchRange,
    Implicit3DTileContent,
    ImplicitSubdivisionScheme,
    ImplicitTileCoordinates,
    ImplicitTileset,
    Matrix3,
    Matrix4,
    MetadataClass,
    GroupMetadata,
    Multiple3DTileContent,
    Resource,
    TileBoundingSphere,
    TileBoundingS2Cell,
  } from "../../Source/Cesium.js";
  import CesiumMath from "../../Source/Core/Math.js";
  import ImplicitTilingTester from "../ImplicitTilingTester.js";
  import Cesium3DTilesTester from "../Cesium3DTilesTester.js";
  import createScene from "../createScene.js";
  
  describe(
    "Scene/Implicit3DTileContent",
    function () {
      var tilesetResource = new Resource({
        url: "https://example.com/tileset.json",
      });
      var mockTileset = {
        modelMatrix: Matrix4.IDENTITY,
      };
      var metadataSchema; // intentionally left undefined
  
      var tileJson = {
        geometricError: 800,
        refine: "ADD",
        boundingVolume: {
          box: [0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256],
        },
        content: {
          uri: "https://example.com/{level}/{x}/{y}.b3dm",
          extras: {
            author: "Cesium",
          },
        },
        extensions: {
          "3DTILES_implicit_tiling": {
            subdivisionScheme: "QUADTREE",
            subtreeLevels: 2,
            maximumLevel: 1,
            subtrees: {
              uri: "https://example.com/{level}/{x}/{y}.subtree",
            },
          },
        },
        extras: {
          year: "2021",
        },
      };
  
      var implicitTileset = new ImplicitTileset(
        tilesetResource,
        tileJson,
        metadataSchema
      );
  
      var quadtreeBuffer = ImplicitTilingTester.generateSubtreeBuffers({
        tileAvailability: {
          descriptor: "11010",
          bitLength: 5,
          isInternal: true,
        },
        contentAvailability: [
          {
            descriptor: "01010",
            bitLength: 5,
            isInternal: true,
          },
        ],
        childSubtreeAvailability: {
          descriptor: "1111000011110000",
          bitLength: 16,
          isInternal: true,
        },
      }).subtreeBuffer;
  
      var rootCoordinates = new ImplicitTileCoordinates({
        subdivisionScheme: implicitTileset.subdivisionScheme,
        subtreeLevels: implicitTileset.subtreeLevels,
        level: 0,
        x: 0,
        y: 0,
        z: 0,
      });
  
      function gatherTilesPreorder(tile, minLevel, maxLevel, result) {
        var level = tile.implicitCoordinates.level;
        if (minLevel <= level && level <= maxLevel) {
          result.push(tile);
        }
  
        for (var i = 0; i < tile.children.length; i++) {
          gatherTilesPreorder(tile.children[i], minLevel, maxLevel, result);
        }
      }
  
      function getBoundingBoxArray(tile) {
        var boundingBox = tile.boundingVolume.boundingVolume;
        var box = new Array(12);
        Cartesian3.pack(boundingBox.center, box);
        Matrix3.pack(boundingBox.halfAxes, box, 3);
        return box;
      }
  
      var scene;
  
      // This scene is the same as Composite/Composite, just rephrased
      // using 3DTILES_multiple_contents
      var centerLongitude = -1.31968;
      var centerLatitude = 0.698874;
  
      beforeAll(function () {
        scene = createScene();
      });
  
      afterAll(function () {
        scene.destroyForSpecs();
      });
  
      var mockPlaceholderTile;
      beforeEach(function () {
        mockPlaceholderTile = new Cesium3DTile(mockTileset, tilesetResource, {
          geometricError: 400,
          boundingVolume: {
            box: [0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256],
          },
        });
        mockPlaceholderTile.implicitCoordinates = rootCoordinates;
        mockPlaceholderTile.implicitTileset = implicitTileset;
  
        // One item in each data set is always located in the center, so point the camera there
        var center = Cartesian3.fromRadians(centerLongitude, centerLatitude);
        scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 26.0));
      });
  
      afterEach(function () {
        scene.primitives.removeAll();
      });
  
      it("expands subtree", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var expectedChildrenCounts = [2, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0];
          var tiles = [];
          var subtreeRootTile = mockPlaceholderTile.children[0];
          gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
          expect(expectedChildrenCounts.length).toEqual(tiles.length);
          for (var i = 0; i < tiles.length; i++) {
            expect(tiles[i].children.length).toEqual(expectedChildrenCounts[i]);
          }
        });
      });
  
      it("sets tile coordinates on each tile", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var expectedCoordinates = [
            [0, 0, 0],
            [1, 0, 0],
            [2, 0, 0],
            [2, 1, 0],
            [2, 0, 1],
            [2, 1, 1],
            [1, 0, 1],
            [2, 0, 2],
            [2, 1, 2],
            [2, 0, 3],
            [2, 1, 3],
          ];
          var tiles = [];
          var subtreeRootTile = mockPlaceholderTile.children[0];
          gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
          for (var i = 0; i < tiles.length; i++) {
            var expected = expectedCoordinates[i];
            var coordinates = new ImplicitTileCoordinates({
              subdivisionScheme: implicitTileset.subdivisionScheme,
              subtreeLevels: implicitTileset.subtreeLevels,
              level: expected[0],
              x: expected[1],
              y: expected[2],
            });
            expect(tiles[i].implicitCoordinates).toEqual(coordinates);
          }
        });
      });
  
      it("handles deeper subtrees correctly", function () {
        mockPlaceholderTile.implicitCoordinates = new ImplicitTileCoordinates({
          subdivisionScheme: implicitTileset.subdivisionScheme,
          subtreeLevels: implicitTileset.subtreeLevels,
          level: 2,
          x: 2,
          y: 1,
        });
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        var refine =
          implicitTileset.refine === "ADD"
            ? Cesium3DTileRefine.ADD
            : Cesium3DTileRefine.REPLACE;
  
        var parentCoordinates = mockPlaceholderTile.implicitCoordinates;
        var childCoordinates = parentCoordinates.getChildCoordinates(0);
  
        var parentGeometricError = implicitTileset.geometricError / 4;
        var childGeometricError = implicitTileset.geometricError / 8;
  
        var rootBoundingVolume = [0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256];
        var parentBox = Implicit3DTileContent._deriveBoundingBox(
          rootBoundingVolume,
          parentCoordinates.level,
          parentCoordinates.x,
          parentCoordinates.y
        );
        var childBox = Implicit3DTileContent._deriveBoundingBox(
          rootBoundingVolume,
          childCoordinates.level,
          childCoordinates.x,
          childCoordinates.y
        );
  
        return content.readyPromise.then(function () {
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var childTile = subtreeRootTile.children[0];
          expect(subtreeRootTile.implicitCoordinates).toEqual(parentCoordinates);
          expect(childTile.implicitCoordinates).toEqual(childCoordinates);
  
          expect(subtreeRootTile.refine).toEqual(refine);
          expect(childTile.refine).toEqual(refine);
  
          expect(subtreeRootTile.geometricError).toEqual(parentGeometricError);
          expect(childTile.geometricError).toEqual(childGeometricError);
  
          expect(getBoundingBoxArray(subtreeRootTile)).toEqual(parentBox);
          expect(getBoundingBoxArray(childTile)).toEqual(childBox);
        });
      });
  
      it("puts the root tile inside the placeholder tile", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          expect(mockPlaceholderTile.children.length).toEqual(1);
        });
      });
  
      it("preserves tile extras", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          expect(mockPlaceholderTile.children[0].extras).toEqual(tileJson.extras);
        });
      });
  
      it("stores a reference to the subtree in each transcoded tile", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined();
  
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var subtree = subtreeRootTile.implicitSubtree;
          expect(subtree).toBeDefined();
  
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 0, 1, tiles);
          for (var i = 0; i < tiles.length; i++) {
            expect(tiles[i].implicitSubtree).toBe(subtree);
          }
        });
      });
  
      it("does not store references to subtrees in placeholder tiles", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          expect(mockPlaceholderTile.implicitSubtree).not.toBeDefined();
  
          var subtreeRootTile = mockPlaceholderTile.children[0];
  
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 2, 2, tiles);
          for (var i = 0; i < tiles.length; i++) {
            expect(tiles[i].implicitSubtree).not.toBeDefined();
          }
        });
      });
  
      it("destroys", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var subtree = content._implicitSubtree;
          expect(content.isDestroyed()).toBe(false);
          expect(subtree.isDestroyed()).toBe(false);
  
          content.destroy();
          expect(content.isDestroyed()).toBe(true);
          expect(subtree.isDestroyed()).toBe(true);
        });
      });
  
      it("returns default values for most Cesium3DTileContent properties", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
  
        expect(content.featurePropertiesDirty).toBe(false);
        expect(content.featuresLength).toBe(0);
        expect(content.pointsLength).toBe(0);
        expect(content.trianglesLength).toBe(0);
        expect(content.geometryByteLength).toBe(0);
        expect(content.texturesByteLength).toBe(0);
        expect(content.batchTableByteLength).toBe(0);
        expect(content.innerContents).not.toBeDefined();
        expect(content.tileset).toBe(mockTileset);
        expect(content.tile).toBe(mockPlaceholderTile);
        expect(content.batchTable).not.toBeDefined();
      });
  
      it("url returns the subtree url", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        expect(content.url).toBe("https://example.com/0/0/0.subtree");
      });
  
      it("templates content URIs for each tile with content", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var expectedCoordinates = [
            [0, 0, 0],
            [1, 0, 0],
            [1, 0, 1],
          ];
          var contentAvailability = [false, true, true];
          var templateUri = implicitTileset.contentUriTemplates[0];
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 0, 1, tiles);
          expect(expectedCoordinates.length).toEqual(tiles.length);
          for (var i = 0; i < tiles.length; i++) {
            var expected = expectedCoordinates[i];
            var coordinates = new ImplicitTileCoordinates({
              subdivisionScheme: implicitTileset.subdivisionScheme,
              subtreeLevels: implicitTileset.subtreeLevels,
              level: expected[0],
              x: expected[1],
              y: expected[2],
            });
            var expectedResource = templateUri.getDerivedResource({
              templateValues: coordinates.getTemplateValues(),
            });
            if (contentAvailability[i]) {
              expect(tiles[i]._contentResource.url).toEqual(expectedResource.url);
            } else {
              expect(tiles[i]._contentResource).not.toBeDefined();
            }
          }
        });
      });
  
      it("constructs placeholder tiles for child subtrees", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var expectedCoordinates = [
            [2, 0, 0],
            [2, 1, 0],
            [2, 0, 1],
            [2, 1, 1],
            [2, 0, 2],
            [2, 1, 2],
            [2, 0, 3],
            [2, 1, 3],
          ];
          var templateUri = implicitTileset.subtreeUriTemplate;
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 2, 2, tiles);
  
          expect(expectedCoordinates.length).toEqual(tiles.length);
          for (var i = 0; i < tiles.length; i++) {
            var expected = expectedCoordinates[i];
            var coordinates = new ImplicitTileCoordinates({
              subdivisionScheme: implicitTileset.subdivisionScheme,
              subtreeLevels: implicitTileset.subtreeLevels,
              level: expected[0],
              x: expected[1],
              y: expected[2],
            });
            var expectedResource = templateUri.getDerivedResource({
              templateValues: coordinates.getTemplateValues(),
            });
            var placeholderTile = tiles[i];
            expect(placeholderTile._contentResource.url).toEqual(
              expectedResource.url
            );
            expect(placeholderTile.implicitTileset).toBeDefined();
            expect(placeholderTile.implicitCoordinates).toBeDefined();
          }
        });
      });
  
      it("propagates refine down the tree", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        var refine =
          implicitTileset.refine === "ADD"
            ? Cesium3DTileRefine.ADD
            : Cesium3DTileRefine.REPLACE;
        return content.readyPromise.then(function () {
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
          for (var i = 0; i < tiles.length; i++) {
            expect(tiles[i].refine).toEqual(refine);
          }
        });
      });
  
      it("divides the geometricError by 2 for each level of the tree", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        var rootGeometricError = implicitTileset.geometricError;
        return content.readyPromise.then(function () {
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
          for (var i = 0; i < tiles.length; i++) {
            var level = tiles[i].implicitCoordinates.level;
            expect(tiles[i].geometricError).toEqual(
              rootGeometricError / Math.pow(2, level)
            );
          }
        });
      });
  
      it("subdivides bounding volumes for each tile", function () {
        var content = new Implicit3DTileContent(
          mockTileset,
          mockPlaceholderTile,
          tilesetResource,
          quadtreeBuffer,
          0
        );
        return content.readyPromise.then(function () {
          var expectedCoordinates = [
            [0, 0, 0],
            [1, 0, 0],
            [2, 0, 0],
            [2, 1, 0],
            [2, 0, 1],
            [2, 1, 1],
            [1, 0, 1],
            [2, 0, 2],
            [2, 1, 2],
            [2, 0, 3],
            [2, 1, 3],
          ];
          var rootBoundingVolume = [0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 256];
  
          var subtreeRootTile = mockPlaceholderTile.children[0];
          var tiles = [];
          gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
  
          expect(expectedCoordinates.length).toEqual(tiles.length);
          for (var i = 0; i < tiles.length; i++) {
            var coordinates = expectedCoordinates[i];
  
            var boundingBox = tiles[i].boundingVolume.boundingVolume;
            var childBox = new Array(12);
            Cartesian3.pack(boundingBox.center, childBox);
            Matrix3.pack(boundingBox.halfAxes, childBox, 3);
  
            var expectedBounds = Implicit3DTileContent._deriveBoundingBox(
              rootBoundingVolume,
              coordinates[0],
              coordinates[1],
              coordinates[2]
            );
            expect(childBox).toEqual(expectedBounds);
          }
        });
      });
  
      describe("_deriveBoundingVolumeS2", function () {
        var deriveBoundingVolumeS2 =
          Implicit3DTileContent._deriveBoundingVolumeS2;
        var simpleBoundingVolumeS2 = {
          token: "1",
          minimumHeight: 0,
          maximumHeight: 10,
        };
        var simpleBoundingVolumeS2Cell = new TileBoundingS2Cell(
          simpleBoundingVolumeS2
        );
        var implicitTilesetS2 = {
          boundingVolume: {
            extensions: {
              "3DTILES_bounding_volume_S2": simpleBoundingVolumeS2,
            },
          },
          subdivisionScheme: ImplicitSubdivisionScheme.QUADTREE,
        };
  
        it("throws if implicitTileset is undefined", function () {
          expect(function () {
            deriveBoundingVolumeS2(undefined, {}, false, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if parentTile is undefined", function () {
          expect(function () {
            deriveBoundingVolumeS2({}, undefined, false, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if parentIsPlaceholderTile is undefined", function () {
          expect(function () {
            deriveBoundingVolumeS2({}, {}, undefined, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if childIndex is undefined", function () {
          expect(function () {
            deriveBoundingVolumeS2({}, {}, false, undefined);
          }).toThrowDeveloperError();
        });
  
        it("returns implicit tileset boundingVolume if parentIsPlaceholderTile is true", function () {
          var placeholderTile = {
            _boundingVolume: simpleBoundingVolumeS2Cell,
          };
          var result = deriveBoundingVolumeS2(
            implicitTilesetS2,
            placeholderTile,
            true,
            0
          );
          expect(result).toEqual(implicitTilesetS2.boundingVolume);
          expect(result).not.toBe(implicitTilesetS2.boundingVolume);
        });
  
        it("subdivides correctly using QUADTREE", function () {
          var parentTile = {
            _boundingVolume: simpleBoundingVolumeS2Cell,
          };
          var expected = {
            token: "04",
            minimumHeight: 0,
            maximumHeight: 10,
          };
          var result = deriveBoundingVolumeS2(
            implicitTilesetS2,
            parentTile,
            false,
            0
          );
          expect(result).toEqual({
            extensions: {
              "3DTILES_bounding_volume_S2": expected,
            },
          });
        });
  
        it("subdivides correctly using OCTREE", function () {
          implicitTilesetS2.subdivisionScheme = ImplicitSubdivisionScheme.OCTREE;
          var parentTile = {
            _boundingVolume: simpleBoundingVolumeS2Cell,
          };
          var expected0 = {
            token: "04",
            minimumHeight: 0,
            maximumHeight: 5,
          };
          var expected1 = {
            token: "04",
            minimumHeight: 5,
            maximumHeight: 10,
          };
          var result0 = deriveBoundingVolumeS2(
            implicitTilesetS2,
            parentTile,
            false,
            0
          );
          expect(result0).toEqual({
            extensions: {
              "3DTILES_bounding_volume_S2": expected0,
            },
          });
          var result1 = deriveBoundingVolumeS2(
            implicitTilesetS2,
            parentTile,
            false,
            4
          );
          expect(result1).toEqual({
            extensions: {
              "3DTILES_bounding_volume_S2": expected1,
            },
          });
        });
      });
  
      describe("_deriveBoundingBox", function () {
        var deriveBoundingBox = Implicit3DTileContent._deriveBoundingBox;
        var simpleBoundingBox = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1];
        it("throws if rootBox is undefined", function () {
          expect(function () {
            deriveBoundingBox(undefined, 0, 0, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if level is undefined", function () {
          expect(function () {
            deriveBoundingBox(simpleBoundingBox, undefined, 0, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if x is undefined", function () {
          expect(function () {
            deriveBoundingBox(simpleBoundingBox, 1, undefined, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if y is undefined", function () {
          expect(function () {
            deriveBoundingBox(simpleBoundingBox, 1, 0, undefined);
          }).toThrowDeveloperError();
        });
  
        it("subdivides like a quadtree if z is not given", function () {
          var tile = [1, 3, 0, 4, 0, 0, 0, 2, 0, 0, 0, 1];
          var expected = [0, 1.5, 0, 1, 0, 0, 0, 0.5, 0, 0, 0, 1];
          var result = deriveBoundingBox(tile, 2, 1, 0);
          expect(result).toEqual(expected);
        });
  
        it("subdivides like an octree if z is given", function () {
          var tile = [1, 3, 0, 4, 0, 0, 0, 2, 0, 0, 0, 1];
          var expected = [0, 1.5, 0.25, 1, 0, 0, 0, 0.5, 0, 0, 0, 0.25];
          var result = deriveBoundingBox(tile, 2, 1, 0, 2);
          expect(result).toEqual(expected);
        });
  
        it("handles rotation and non-uniform scaling correctly", function () {
          var tile = [1, 2, 3, 0, 4, 0, 0, 0, 2, 1, 0, 0];
          var expected = [1.25, 1, 1.5, 0, 1, 0, 0, 0, 0.5, 0.25, 0, 0];
          var result = deriveBoundingBox(tile, 2, 1, 0, 2);
          expect(result).toEqual(expected);
        });
      });
  
      describe("_deriveBoundingRegion", function () {
        var deriveBoundingRegion = Implicit3DTileContent._deriveBoundingRegion;
        var simpleRegion = [
          0,
          0,
          CesiumMath.PI_OVER_FOUR,
          CesiumMath.PI_OVER_FOUR,
          0,
          100,
        ];
        it("throws if rootRegion is undefined", function () {
          expect(function () {
            deriveBoundingRegion(undefined, 1, 0, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if level is undefined", function () {
          expect(function () {
            deriveBoundingRegion(simpleRegion, undefined, 0, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if x is undefined", function () {
          expect(function () {
            deriveBoundingRegion(simpleRegion, 1, undefined, 0);
          }).toThrowDeveloperError();
        });
  
        it("throws if y is undefined", function () {
          expect(function () {
            deriveBoundingRegion(simpleRegion, 1, 0, undefined);
          }).toThrowDeveloperError();
        });
  
        function makeRectangle(
          westDegrees,
          southDegrees,
          eastDegrees,
          northDegrees,
          minimumHeight,
          maximumHeight
        ) {
          return [
            CesiumMath.toRadians(westDegrees),
            CesiumMath.toRadians(southDegrees),
            CesiumMath.toRadians(eastDegrees),
            CesiumMath.toRadians(northDegrees),
            minimumHeight,
            maximumHeight,
          ];
        }
  
        it("subdivides like a quadtree if z is not given", function () {
          // 4x4 degree rectangle so the subdivisions at level 2 will
          // be 1 degree each
          var tile = makeRectangle(-1, -2, 3, 2, 0, 20);
          var expected = makeRectangle(0, 1, 1, 2, 0, 20);
  
          var result = deriveBoundingRegion(tile, 2, 1, 3);
          expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON9);
        });
  
        it("deriveVolume subdivides like an octree if z is given", function () {
          // 4x4 degree rectangle so the subdivisions at level 2 will
          // be 1 degree each
          var tile = makeRectangle(-1, -2, 3, 2, 0, 20);
          var expected = makeRectangle(0, 1, 1, 2, 10, 15);
  
          var result = deriveBoundingRegion(tile, 2, 1, 3, 2);
          expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON9);
        });
  
        it("handles the IDL", function () {
          var tile = makeRectangle(90, -45, -90, 45, 0, 20);
          var expected = makeRectangle(180, -45, -90, 0, 0, 20);
  
          var result = deriveBoundingRegion(tile, 1, 1, 0);
          expect(result).toEqualEpsilon(expected, CesiumMath.EPSILON9);
        });
      });
  
      describe("3DTILES_multiple_contents", function () {
        var implicitMultipleContentsUrl =
          "Data/Cesium3DTiles/Implicit/ImplicitMultipleContents/tileset.json";
  
        it("a single content is transcoded as a regular tile", function () {
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitMultipleContentsUrl
          ).then(function (tileset) {
            // The root tile of this tileset only has one available content
            var transcodedRoot = tileset.root.children[0];
            var transcodedRootHeader = transcodedRoot._header;
            expect(transcodedRoot.content).toBeInstanceOf(
              Batched3DModel3DTileContent
            );
            expect(transcodedRootHeader.content).toEqual({
              uri: "ground/0/0/0.b3dm",
            });
            expect(transcodedRootHeader.extensions).not.toBeDefined();
          });
        });
  
        it("multiple contents are transcoded to a tile with a 3DTILES_multiple_contents extension", function () {
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitMultipleContentsUrl
          ).then(function (tileset) {
            var childTiles = tileset.root.children[0].children;
            for (var i = 0; i < childTiles.length; i++) {
              var childTile = childTiles[i];
              var content = childTile.content;
              expect(content).toBeInstanceOf(Multiple3DTileContent);
  
              var childTileHeader = childTile._header;
              expect(childTileHeader.content).not.toBeDefined();
            }
          });
        });
  
        it("passes extensions through correctly", function () {
          var originalLoadJson = Cesium3DTileset.loadJson;
          var metadataExtension = {
            group: "buildings",
          };
          var otherExtension = {
            someKey: "someValue",
          };
  
          spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) {
            return originalLoadJson(tilesetUrl).then(function (tilesetJson) {
              var multiContent =
                tilesetJson.root.extensions["3DTILES_multiple_contents"];
              multiContent.content.forEach(function (content) {
                content.extensions = {
                  "3DTILES_metadata": metadataExtension,
                };
              });
  
              tilesetJson.root.extensions["3DTILES_extension"] = otherExtension;
              return tilesetJson;
            });
          });
  
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitMultipleContentsUrl
          ).then(function (tileset) {
            // the placeholder tile does not have any extensions.
            var placeholderTile = tileset.root;
            var placeholderHeader = placeholderTile._header;
            expect(placeholderHeader.extensions).not.toBeDefined();
            expect(placeholderHeader.content.extensions).not.toBeDefined();
  
            var transcodedRoot = placeholderTile.children[0];
            var transcodedRootHeader = transcodedRoot._header;
            expect(transcodedRootHeader.extensions).toEqual({
              "3DTILES_extension": otherExtension,
            });
            expect(transcodedRootHeader.content.extensions).toEqual({
              "3DTILES_metadata": metadataExtension,
            });
  
            var childTiles = transcodedRoot.children;
            for (var i = 0; i < childTiles.length; i++) {
              var childTile = childTiles[i];
  
              var childTileHeader = childTile._header;
              expect(childTileHeader.extensions["3DTILES_extension"]).toEqual(
                otherExtension
              );
  
              var innerContentHeaders =
                childTileHeader.extensions["3DTILES_multiple_contents"].content;
  
              innerContentHeaders.forEach(function (header) {
                expect(header.extensions).toEqual({
                  "3DTILES_metadata": metadataExtension,
                });
              });
            }
          });
        });
      });
  
      describe("3DTILES_metadata", function () {
        var implicitTilesetUrl =
          "Data/Cesium3DTiles/Implicit/ImplicitTileset/tileset.json";
        var implicitGroupMetadataUrl =
          "Data/Cesium3DTiles/Metadata/ImplicitGroupMetadata/tileset.json";
        var implicitHeightSemanticsUrl =
          "Data/Cesium3DTiles/Metadata/ImplicitHeightSemantics/tileset.json";
        var implicitS2HeightSemanticsUrl =
          "Data/Cesium3DTiles/Metadata/ImplicitHeightSemantics/s2-tileset.json";
        var implicitTileBoundingVolumeSemanticsUrl =
          "Data/Cesium3DTiles/Metadata/ImplicitTileBoundingVolumeSemantics/tileset.json";
        var implicitContentBoundingVolumeSemanticsUrl =
          "Data/Cesium3DTiles/Metadata/ImplicitContentBoundingVolumeSemantics/tileset.json";
  
        var metadataClass = new MetadataClass({
          id: "test",
          class: {
            properties: {
              name: {
                type: "STRING",
              },
              height: {
                type: "FLOAT32",
              },
            },
          },
        });
        var groupMetadata = new GroupMetadata({
          id: "testGroup",
          group: {
            properties: {
              name: "Test Group",
              height: 35.6,
            },
          },
          class: metadataClass,
        });
  
        it("assigns groupMetadata", function () {
          return Cesium3DTilesTester.loadTileset(scene, implicitTilesetUrl).then(
            function (tileset) {
              var content = tileset.root.content;
              content.groupMetadata = groupMetadata;
              expect(content.groupMetadata).toBe(groupMetadata);
            }
          );
        });
  
        it("group metadata gets transcoded correctly", function () {
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitGroupMetadataUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root;
            var subtreeRootTile = placeholderTile.children[0];
            var tiles = [];
            gatherTilesPreorder(subtreeRootTile, 0, 2, tiles);
  
            var groups = tileset.metadata.groups;
            var ground = groups.ground;
            expect(ground.getProperty("color")).toEqual(
              new Cartesian3(120, 68, 32)
            );
            expect(ground.getProperty("priority")).toBe(0);
  
            var sky = groups.sky;
            expect(sky.getProperty("color")).toEqual(
              new Cartesian3(206, 237, 242)
            );
            expect(sky.getProperty("priority")).toBe(1);
  
            tiles.forEach(function (tile) {
              if (tile.hasMultipleContents) {
                // child tiles have multiple contents
                var contents = tile.content.innerContents;
                expect(contents[0].groupMetadata).toBe(ground);
                expect(contents[1].groupMetadata).toBe(sky);
              } else {
                // parent tile is a single B3DM tile
                expect(tile.content.groupMetadata).toBe(ground);
              }
            });
          });
        });
  
        // view (lon, lat, height) = (0, 0, 0) from height meters above
        function viewCartographicOrigin(height) {
          var center = Cartesian3.fromDegrees(0.0, 0.0);
          var offset = new Cartesian3(0, 0, height);
          scene.camera.lookAt(center, offset);
        }
  
        it("uses height semantics to adjust region bounding volumes", function () {
          viewCartographicOrigin(10000);
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitHeightSemanticsUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root;
            var subtreeRootTile = placeholderTile.children[0];
  
            var implicitRegion =
              placeholderTile.implicitTileset.boundingVolume.region;
            var minimumHeight = implicitRegion[4];
            var maximumHeight = implicitRegion[5];
  
            // This tileset uses TILE_MINIMUM_HEIGHT and TILE_MAXIMUM_HEIGHT
            // to set tighter bounding volumes
            var tiles = [];
            gatherTilesPreorder(subtreeRootTile, 0, 3, tiles);
            for (var i = 0; i < tiles.length; i++) {
              var tileRegion = tiles[i].boundingVolume;
              expect(tileRegion.minimumHeight).toBeGreaterThan(minimumHeight);
              expect(tileRegion.maximumHeight).toBeLessThan(maximumHeight);
            }
          });
        });
  
        it("uses height semantics to adjust S2 bounding volumes", function () {
          viewCartographicOrigin(10000);
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitS2HeightSemanticsUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root;
            var subtreeRootTile = placeholderTile.children[0];
  
            var implicitS2Volume =
              placeholderTile.implicitTileset.boundingVolume.extensions[
                "3DTILES_bounding_volume_S2"
              ];
            var minimumHeight = implicitS2Volume.minimumHeight;
            var maximumHeight = implicitS2Volume.maximumHeight;
  
            // This tileset uses TILE_MINIMUM_HEIGHT and TILE_MAXIMUM_HEIGHT
            // to set tighter bounding volumes
            var tiles = [];
            gatherTilesPreorder(subtreeRootTile, 0, 3, tiles);
            for (var i = 0; i < tiles.length; i++) {
              var tileS2Volume = tiles[i].boundingVolume;
              expect(tileS2Volume.minimumHeight).toBeGreaterThan(minimumHeight);
              expect(tileS2Volume.maximumHeight).toBeLessThan(maximumHeight);
            }
          });
        });
  
        // get half the bounding cube width from the bounding box's
        // halfAxes matrix
        function getHalfWidth(boundingBox) {
          return boundingBox.boundingVolume.halfAxes[0];
        }
  
        it("ignores height semantics if the implicit volume is a box", function () {
          var cameraHeight = 100;
          var rootHalfWidth = 10;
          var originalLoadJson = Cesium3DTileset.loadJson;
          spyOn(Cesium3DTileset, "loadJson").and.callFake(function (tilesetUrl) {
            return originalLoadJson(tilesetUrl).then(function (tilesetJson) {
              tilesetJson.root.boundingVolume = {
                box: [
                  Ellipsoid.WGS84.radii.x + cameraHeight,
                  0,
                  0,
                  rootHalfWidth,
                  0,
                  0,
                  0,
                  rootHalfWidth,
                  0,
                  0,
                  0,
                  rootHalfWidth,
                ],
              };
              return tilesetJson;
            });
          });
  
          viewCartographicOrigin(cameraHeight);
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitHeightSemanticsUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root;
            var subtreeRootTile = placeholderTile.children[0];
  
            var tiles = [];
            gatherTilesPreorder(subtreeRootTile, 0, 3, tiles);
  
            // TILE_MINIMUM_HEIGHT and TILE_MAXIMUM_HEIGHT only apply to
            // regions, so this will check that they are not used.
            tiles.forEach(function (tile) {
              var level = tile.implicitCoordinates.level;
              var halfWidth = getHalfWidth(tile.boundingVolume);
              // Even for floats, divide by 2 operations are exact as long
              // as there is no overflow.
              expect(halfWidth).toEqual(rootHalfWidth / Math.pow(2, level));
            });
          });
        });
  
        it("uses tile bounding volume from metadata semantics if present", function () {
          viewCartographicOrigin(124000);
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitTileBoundingVolumeSemanticsUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root.children[0];
            var subtreeRootTile = placeholderTile.children[0];
  
            var rootHalfWidth = 2048;
            expect(getHalfWidth(subtreeRootTile.boundingVolume)).toBe(
              rootHalfWidth
            );
  
            for (var level = 1; level < 4; level++) {
              var halfWidthAtLevel = rootHalfWidth / (1 << level);
              var tiles = [];
              gatherTilesPreorder(subtreeRootTile, level, level, tiles);
              for (var i = 0; i < tiles.length; i++) {
                // In this tileset, each tile's TILE_BOUNDING_BOX is
                // smaller than the implicit tile bounds. Make sure
                // this is true.
                var tile = tiles[i];
                var halfWidth = getHalfWidth(tile.boundingVolume);
                expect(halfWidth).toBeLessThan(halfWidthAtLevel);
              }
            }
          });
        });
  
        it("uses content bounding volume from metadata semantics if present", function () {
          viewCartographicOrigin(124000);
          return Cesium3DTilesTester.loadTileset(
            scene,
            implicitContentBoundingVolumeSemanticsUrl
          ).then(function (tileset) {
            var placeholderTile = tileset.root.children[0];
            var subtreeRootTile = placeholderTile.children[0];
  
            // This tileset defines the content bounding spheres in a
            // property with metadata semantic CONTENT_BOUNDING_SPHERE.
            // Check that each tile has a content bounding volume.
            var tiles = [];
            gatherTilesPreorder(subtreeRootTile, 0, 3, tiles);
            tiles.forEach(function (tile) {
              expect(
                tile.contentBoundingVolume instanceof TileBoundingSphere
              ).toBe(true);
              expect(tile.contentBoundingVolume).not.toBe(tile.boundingVolume);
            });
          });
        });
      });
    },
    "WebGL"
  );
  