function Cesium3DTile(tileset, baseResource, header, parent) {
  this._tileset = tileset;
  this._header = header;

  const hasContentsArray = defined(header.contents);
  const hasMultipleContents =
    (hasContentsArray && header.contents.length > 1) ||
    hasExtension(header, "3DTILES_multiple_contents");

  // In the 1.0 schema, content is stored in tile.content instead of tile.contents
  const contentHeader =
    hasContentsArray && !hasMultipleContents
      ? header.contents[0]
      : header.content;

  this._contentHeader = contentHeader;

  /**
   * The local transform of this tile.
   * @type {Matrix4}
   */
  this.transform = defined(header.transform)
    ? Matrix4.unpack(header.transform)
    : Matrix4.clone(Matrix4.IDENTITY);

  const parentTransform = defined(parent)
    ? parent.computedTransform
    : tileset.modelMatrix;
  const computedTransform = Matrix4.multiply(
    parentTransform,
    this.transform,
    new Matrix4(),
  );

  const parentInitialTransform = defined(parent)
    ? parent._initialTransform
    : Matrix4.IDENTITY;
  this._initialTransform = Matrix4.multiply(
    parentInitialTransform,
    this.transform,
    new Matrix4(),
  );

  /**
   * The final computed transform of this tile.
   * @type {Matrix4}
   * @readonly
   */
  this.computedTransform = computedTransform;

  /**
   * When tile metadata is present (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension is used,
   * this stores a {@link TileMetadata} object for accessing tile metadata.
   *
   * @type {TileMetadata}
   * @readonly
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.metadata = findTileMetadata(tileset, header);

  this._verticalExaggeration = 1.0;
  this._verticalExaggerationRelativeHeight = 0.0;

  // Important: tile metadata must be parsed before this line so that the
  // metadata semantics TILE_BOUNDING_BOX, TILE_BOUNDING_REGION, or TILE_BOUNDING_SPHERE
  // can override header.boundingVolume (if necessary)
  this._boundingVolume = this.createBoundingVolume(
    header.boundingVolume,
    computedTransform,
  );
  this._boundingVolume2D = undefined;

  let contentBoundingVolume;

  if (defined(contentHeader) && defined(contentHeader.boundingVolume)) {
    // Non-leaf tiles may have a content bounding-volume, which is a tight-fit bounding volume
    // around only the features in the tile.  This box is useful for culling for rendering,
    // but not for culling for traversing the tree since it does not guarantee spatial coherence, i.e.,
    // since it only bounds features in the tile, not the entire tile, children may be
    // outside of this box.
    contentBoundingVolume = this.createBoundingVolume(
      contentHeader.boundingVolume,
      computedTransform,
    );
  }
  this._contentBoundingVolume = contentBoundingVolume;
  this._contentBoundingVolume2D = undefined;

  let viewerRequestVolume;
  if (defined(header.viewerRequestVolume)) {
    viewerRequestVolume = this.createBoundingVolume(
      header.viewerRequestVolume,
      computedTransform,
    );
  }
  this._viewerRequestVolume = viewerRequestVolume;

  /**
   * The error, in meters, introduced if this tile is rendered and its children are not.
   * This is used to compute screen space error, i.e., the error measured in pixels.
   *
   * @type {number}
   * @readonly
   */
  this.geometricError = header.geometricError;
  this._geometricError = header.geometricError;

  if (!defined(this._geometricError)) {
    this._geometricError = defined(parent)
      ? parent._geometricError
      : tileset._geometricError;
    Cesium3DTile._deprecationWarning(
      "geometricErrorUndefined",
      "Required property geometricError is undefined for this tile. Using parent's geometric error instead.",
    );
  }

  this.updateGeometricErrorScale();

  let refine;
  if (defined(header.refine)) {
    if (header.refine === "replace" || header.refine === "add") {
      Cesium3DTile._deprecationWarning(
        "lowercase-refine",
        `This tile uses a lowercase refine "${
          header.refine
        }". Instead use "${header.refine.toUpperCase()}".`,
      );
    }
    refine =
      header.refine.toUpperCase() === "REPLACE"
        ? Cesium3DTileRefine.REPLACE
        : Cesium3DTileRefine.ADD;
  } else if (defined(parent)) {
    // Inherit from parent tile if omitted.
    refine = parent.refine;
  } else {
    refine = Cesium3DTileRefine.REPLACE;
  }

  /**
   * Specifies the type of refinement that is used when traversing this tile for rendering.
   *
   * @type {Cesium3DTileRefine}
   * @readonly
   * @private
   */
  this.refine = refine;

  /**
   * Gets the tile's children.
   *
   * @type {Cesium3DTile[]}
   * @readonly
   */
  this.children = [];

  /**
   * This tile's parent or <code>undefined</code> if this tile is the root.
   * <p>
   * When a tile's content points to an external tileset JSON file, the external tileset's
   * root tile's parent is not <code>undefined</code>; instead, the parent references
   * the tile (with its content pointing to an external tileset JSON file) as if the two tilesets were merged.
   * </p>
   *
   * @type {Cesium3DTile}
   * @readonly
   */
  this.parent = parent;

  let content;
  let hasEmptyContent = false;
  let contentState;
  let contentResource;
  let serverKey;

  baseResource = Resource.createIfNeeded(baseResource);

  if (hasMultipleContents) {
    contentState = Cesium3DTileContentState.UNLOADED;
    // Each content may have its own URI, but they all need to be resolved
    // relative to the tileset, so the base resource is used.
    contentResource = baseResource.clone();
  } else if (defined(contentHeader)) {
    let contentHeaderUri = contentHeader.uri;
    if (defined(contentHeader.url)) {
      Cesium3DTile._deprecationWarning(
        "contentUrl",
        'This tileset JSON uses the "content.url" property which has been deprecated. Use "content.uri" instead.',
      );
      contentHeaderUri = contentHeader.url;
    }
    if (contentHeaderUri === "") {
      Cesium3DTile._deprecationWarning(
        "contentUriEmpty",
        "content.uri property is an empty string, which creates a circular dependency, making this tileset invalid. Omit the content property instead",
      );
      content = new Empty3DTileContent(tileset, this);
      hasEmptyContent = true;
      contentState = Cesium3DTileContentState.READY;
    } else {
      contentState = Cesium3DTileContentState.UNLOADED;
      contentResource = baseResource.getDerivedResource({
        url: contentHeaderUri,
      });
      serverKey = RequestScheduler.getServerKey(
        contentResource.getUrlComponent(),
      );
    }
  } else {
    content = new Empty3DTileContent(tileset, this);
    hasEmptyContent = true;
    contentState = Cesium3DTileContentState.READY;
  }

  this._content = content;
  this._contentResource = contentResource;
  this._contentState = contentState;
  this._expiredContent = undefined;

  this._serverKey = serverKey;

  /**
   * When <code>true</code>, the tile has no content.
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   */
  this.hasEmptyContent = hasEmptyContent;

  /**
   * When <code>true</code>, the tile's content points to an external tileset.
   * <p>
   * This is <code>false</code> until the tile's content is loaded.
   * </p>
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   */
  this.hasTilesetContent = false;

  /**
   * When <code>true</code>, the tile's content is an implicit tileset.
   * <p>
   * This is <code>false</code> until the tile's implicit content is loaded.
   * </p>
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.hasImplicitContent = false;

  /**
   * Determines whether the tile has renderable content.
   *
   * The loading starts with the assumption that the tile does have
   * renderable content, if the content is not empty.<br>
   * <br>
   * This turns <code>false</code> only when the tile content is loaded
   * and turns out to be a single content that points to an external
   * tileset or implicit content
   * </p>
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   */
  this.hasRenderableContent = !hasEmptyContent;

  /**
   * When <code>true</code>, the tile contains content metadata from implicit tiling. This flag is set
   * for tiles transcoded by <code>Implicit3DTileContent</code>.
   * <p>
   * This is <code>false</code> until the tile's content is loaded.
   * </p>
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.hasImplicitContentMetadata = false;

  /**
   * When <code>true</code>, the tile has multiple contents, either in the tile JSON (3D Tiles 1.1)
   * or via the <code>3DTILES_multiple_contents</code> extension.
   *
   * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_multiple_contents|3DTILES_multiple_contents extension}
   *
   * @type {boolean}
   * @readonly
   *
   * @private
   */
  this.hasMultipleContents = hasMultipleContents;

  /**
   * The node in the tileset's LRU cache, used to determine when to unload a tile's content.
   *
   * See {@link Cesium3DTilesetCache}
   *
   * @type {DoublyLinkedListNode}
   * @readonly
   *
   * @private
   */
  this.cacheNode = undefined;

  const expire = header.expire;
  let expireDuration;
  let expireDate;
  if (defined(expire)) {
    expireDuration = expire.duration;
    if (defined(expire.date)) {
      expireDate = JulianDate.fromIso8601(expire.date);
    }
  }

  /**
   * The time in seconds after the tile's content is ready when the content expires and new content is requested.
   *
   * @type {number}
   */
  this.expireDuration = expireDuration;

  /**
   * The date when the content expires and new content is requested.
   *
   * @type {JulianDate}
   */
  this.expireDate = expireDate;

  /**
   * The time when a style was last applied to this tile.
   *
   * @type {number}
   *
   * @private
   */
  this.lastStyleTime = 0.0;

  /**
   * Marks whether the tile's children bounds are fully contained within the tile's bounds
   *
   * @type {Cesium3DTileOptimizationHint}
   *
   * @private
   */
  this._optimChildrenWithinParent = Cesium3DTileOptimizationHint.NOT_COMPUTED;

  /**
   * Tracks if the tile's relationship with a ClippingPlaneCollection has changed with regards
   * to the ClippingPlaneCollection's state.
   *
   * @type {boolean}
   *
   * @private
   */
  this.clippingPlanesDirty = false;

  /**
   * Tracks if the tile's relationship with a ClippingPolygonCollection has changed with regards
   * to the ClippingPolygonCollection's state.
   *
   * @type {boolean}
   *
   * @private
   */
  this.clippingPolygonsDirty = false;

  /**
   * Tracks if the tile's request should be deferred until all non-deferred
   * tiles load.
   *
   * @type {boolean}
   *
   * @private
   */
  this.priorityDeferred = false;

  /**
   * For implicit tiling, an ImplicitTileset object will be attached to a
   * placeholder tile with either implicit tiling in the JSON (3D Tiles 1.1)
   * or the <code>3DTILES_implicit_tiling</code> extension.
   * This way the {@link Implicit3DTileContent} can access the tile later once the content is fetched.
   *
   * @type {ImplicitTileset|undefined}
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.implicitTileset = undefined;

  /**
   * For implicit tiling, the (level, x, y, [z]) coordinates within the
   * implicit tileset are stored in the tile.
   *
   * @type {ImplicitTileCoordinates|undefined}
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.implicitCoordinates = undefined;

  /**
   * For implicit tiling, each transcoded tile will hold a weak reference to
   * the {@link ImplicitSubtree}.
   *
   * @type {ImplicitSubtree|undefined}
   *
   * @private
   * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
   */
  this.implicitSubtree = undefined;

  // Members that are updated every frame for tree traversal and rendering optimizations:
  this._distanceToCamera = 0.0;
  this._centerZDepth = 0.0;
  this._screenSpaceError = 0.0;
  this._screenSpaceErrorProgressiveResolution = 0.0; // The screen space error at a given screen height of tileset.progressiveResolutionHeightFraction * screenHeight
  this._visibilityPlaneMask = 0;
  this._visible = false;
  this._inRequestVolume = false;

  this._finalResolution = true;
  this._depth = 0;
  this._stackLength = 0;
  this._selectionDepth = 0;

  this._updatedVisibilityFrame = 0;
  this._touchedFrame = 0;
  this._visitedFrame = 0;
  this._selectedFrame = 0;
  this._wasSelectedLastFrame = false;
  this._requestedFrame = 0;
  this._ancestorWithContent = undefined;
  this._ancestorWithContentAvailable = undefined;
  this._refines = false;
  this._shouldSelect = false;
  this._isClipped = true;
  this._isClippedByPolygon = false;
  this._clippingPlanesState = 0; // encapsulates (_isClipped, clippingPlanes.enabled) and number/function
  this._clippingPolygonsState = 0; // encapsulates (_isClipped, clippingPolygons.enabled) and number/function
  this._debugBoundingVolume = undefined;
  this._debugContentBoundingVolume = undefined;
  this._debugViewerRequestVolume = undefined;
  this._debugColor = Color.fromRandom({ alpha: 1.0 });
  this._debugColorizeTiles = false;

  this._priority = 0.0; // The priority used for request sorting
  this._priorityHolder = this; // Reference to the ancestor up the tree that holds the _foveatedFactor and _distanceToCamera for all tiles in the refinement chain.
  this._priorityProgressiveResolution = false;
  this._priorityProgressiveResolutionScreenSpaceErrorLeaf = false;
  this._priorityReverseScreenSpaceError = 0.0;
  this._foveatedFactor = 0.0;
  this._wasMinPriorityChild = false; // Needed for knowing when to continue a refinement chain. Gets reset in updateTile in traversal and gets set in updateAndPushChildren in traversal.

  this._loadTimestamp = new JulianDate();

  this._commandsLength = 0;

  this._color = undefined;
  this._colorDirty = false;

  this._request = undefined;
}
A tile in a {@link Cesium3DTileset}.  When a tile is first created, its content is not loaded;
the content is loaded on-demand when needed based on the view.
<p>
Do not construct this directly, instead access tiles through {@link Cesium3DTileset#tileVisible}.
</p>

@alias Cesium3DTile
@constructor
@param {Cesium3DTileset} tileset The tileset
@param {Resource} baseResource The base resource for the tileset
@param {object} header The JSON header for the tile
@param {Cesium3DTile} parent The parent tile of the new tile