text: /* This file is automatically rebuilt by the Cesium build process. */
  define(['./Transforms-3ba8e399', './Cartesian2-44e93af5', './Check-285f6bfc', './when-f31b6bd1', './Math-8c161f1c', './ArcType-13a53523', './arrayRemoveDuplicates-618d22a8', './ComponentDatatype-d4a0149c', './EllipsoidGeodesic-fb20d8d5', './EllipsoidRhumbLine-b65d3f48', './EncodedCartesian3-58bad53b', './GeometryAttribute-bd025743', './IntersectionTests-e7442c18', './Plane-b6185d6e', './WebMercatorProjection-f857ca58', './RuntimeError-c7c236f3', './WebGLConstants-34c08bc0'], function (Transforms, Cartesian2, Check, when, _Math, ArcType, arrayRemoveDuplicates, ComponentDatatype, EllipsoidGeodesic, EllipsoidRhumbLine, EncodedCartesian3, GeometryAttribute, IntersectionTests, Plane, WebMercatorProjection, RuntimeError, WebGLConstants) { 'use strict';
  
    /**
     * A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
     * longitude and latitude are directly mapped to X and Y.  This projection is commonly
     * known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
     *
     * @alias GeographicTilingScheme
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
     * the WGS84 ellipsoid.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
     * @param {Number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
     * the tile tree.
     * @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
     * the tile tree.
     */
    function GeographicTilingScheme(options) {
      options = when.defaultValue(options, when.defaultValue.EMPTY_OBJECT);
  
      this._ellipsoid = when.defaultValue(options.ellipsoid, Cartesian2.Ellipsoid.WGS84);
      this._rectangle = when.defaultValue(options.rectangle, Cartesian2.Rectangle.MAX_VALUE);
      this._projection = new Transforms.GeographicProjection(this._ellipsoid);
      this._numberOfLevelZeroTilesX = when.defaultValue(
        options.numberOfLevelZeroTilesX,
        2
      );
      this._numberOfLevelZeroTilesY = when.defaultValue(
        options.numberOfLevelZeroTilesY,
        1
      );
    }
  
    Object.defineProperties(GeographicTilingScheme.prototype, {
      /**
       * Gets the ellipsoid that is tiled by this tiling scheme.
       * @memberof GeographicTilingScheme.prototype
       * @type {Ellipsoid}
       */
      ellipsoid: {
        get: function () {
          return this._ellipsoid;
        },
      },
  
      /**
       * Gets the rectangle, in radians, covered by this tiling scheme.
       * @memberof GeographicTilingScheme.prototype
       * @type {Rectangle}
       */
      rectangle: {
        get: function () {
          return this._rectangle;
        },
      },
  
      /**
       * Gets the map projection used by this tiling scheme.
       * @memberof GeographicTilingScheme.prototype
       * @type {MapProjection}
       */
      projection: {
        get: function () {
          return this._projection;
        },
      },
    });
  
    /**
     * Gets the total number of tiles in the X direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the X direction at the given level.
     */
    GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function (level) {
      return this._numberOfLevelZeroTilesX << level;
    };
  
    /**
     * Gets the total number of tiles in the Y direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the Y direction at the given level.
     */
    GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function (level) {
      return this._numberOfLevelZeroTilesY << level;
    };
  
    /**
     * Transforms a rectangle specified in geodetic radians to the native coordinate system
     * of this tiling scheme.
     *
     * @param {Rectangle} rectangle The rectangle to transform.
     * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
     *          is undefined.
     */
    GeographicTilingScheme.prototype.rectangleToNativeRectangle = function (
      rectangle,
      result
    ) {
      //>>includeStart('debug', pragmas.debug);
      Check.Check.defined("rectangle", rectangle);
      //>>includeEnd('debug');
  
      var west = _Math.CesiumMath.toDegrees(rectangle.west);
      var south = _Math.CesiumMath.toDegrees(rectangle.south);
      var east = _Math.CesiumMath.toDegrees(rectangle.east);
      var north = _Math.CesiumMath.toDegrees(rectangle.north);
  
      if (!when.defined(result)) {
        return new Cartesian2.Rectangle(west, south, east, north);
      }
  
      result.west = west;
      result.south = south;
      result.east = east;
      result.north = north;
      return result;
    };
  
    /**
     * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
     * of the tiling scheme.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.tileXYToNativeRectangle = function (
      x,
      y,
      level,
      result
    ) {
      var rectangleRadians = this.tileXYToRectangle(x, y, level, result);
      rectangleRadians.west = _Math.CesiumMath.toDegrees(rectangleRadians.west);
      rectangleRadians.south = _Math.CesiumMath.toDegrees(rectangleRadians.south);
      rectangleRadians.east = _Math.CesiumMath.toDegrees(rectangleRadians.east);
      rectangleRadians.north = _Math.CesiumMath.toDegrees(rectangleRadians.north);
      return rectangleRadians;
    };
  
    /**
     * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.tileXYToRectangle = function (
      x,
      y,
      level,
      result
    ) {
      var rectangle = this._rectangle;
  
      var xTiles = this.getNumberOfXTilesAtLevel(level);
      var yTiles = this.getNumberOfYTilesAtLevel(level);
  
      var xTileWidth = rectangle.width / xTiles;
      var west = x * xTileWidth + rectangle.west;
      var east = (x + 1) * xTileWidth + rectangle.west;
  
      var yTileHeight = rectangle.height / yTiles;
      var north = rectangle.north - y * yTileHeight;
      var south = rectangle.north - (y + 1) * yTileHeight;
  
      if (!when.defined(result)) {
        result = new Cartesian2.Rectangle(west, south, east, north);
      }
  
      result.west = west;
      result.south = south;
      result.east = east;
      result.north = north;
      return result;
    };
  
    /**
     * Calculates the tile x, y coordinates of the tile containing
     * a given cartographic position.
     *
     * @param {Cartographic} position The position.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.positionToTileXY = function (
      position,
      level,
      result
    ) {
      var rectangle = this._rectangle;
      if (!Cartesian2.Rectangle.contains(rectangle, position)) {
        // outside the bounds of the tiling scheme
        return undefined;
      }
  
      var xTiles = this.getNumberOfXTilesAtLevel(level);
      var yTiles = this.getNumberOfYTilesAtLevel(level);
  
      var xTileWidth = rectangle.width / xTiles;
      var yTileHeight = rectangle.height / yTiles;
  
      var longitude = position.longitude;
      if (rectangle.east < rectangle.west) {
        longitude += _Math.CesiumMath.TWO_PI;
      }
  
      var xTileCoordinate = ((longitude - rectangle.west) / xTileWidth) | 0;
      if (xTileCoordinate >= xTiles) {
        xTileCoordinate = xTiles - 1;
      }
  
      var yTileCoordinate =
        ((rectangle.north - position.latitude) / yTileHeight) | 0;
      if (yTileCoordinate >= yTiles) {
        yTileCoordinate = yTiles - 1;
      }
  
      if (!when.defined(result)) {
        return new Cartesian2.Cartesian2(xTileCoordinate, yTileCoordinate);
      }
  
      result.x = xTileCoordinate;
      result.y = yTileCoordinate;
      return result;
    };
  
    var scratchDiagonalCartesianNE = new Cartesian2.Cartesian3();
    var scratchDiagonalCartesianSW = new Cartesian2.Cartesian3();
    var scratchDiagonalCartographic = new Cartesian2.Cartographic();
    var scratchCenterCartesian = new Cartesian2.Cartesian3();
    var scratchSurfaceCartesian = new Cartesian2.Cartesian3();
  
    var scratchBoundingSphere = new Transforms.BoundingSphere();
    var tilingScheme = new GeographicTilingScheme();
    var scratchCorners = [
      new Cartesian2.Cartographic(),
      new Cartesian2.Cartographic(),
      new Cartesian2.Cartographic(),
      new Cartesian2.Cartographic(),
    ];
    var scratchTileXY = new Cartesian2.Cartesian2();
  
    /**
     * A collection of functions for approximating terrain height
     * @private
     */
    var ApproximateTerrainHeights = {};
  
    /**
     * Initializes the minimum and maximum terrain heights
     * @return {Promise<void>}
     */
    ApproximateTerrainHeights.initialize = function () {
      var initPromise = ApproximateTerrainHeights._initPromise;
      if (when.defined(initPromise)) {
        return initPromise;
      }
  
      var url = require("../Assets/approximateTerrainHeights.json");
      if (/build\/cesiumWorkers\.js$/.test(location.href)) {
        // We're running in a Web Worker, we can't just load by relative path.
        var basePath = location.href.substring(
          0,
          location.href.length - "build/cesiumWorkers.js".length
        );
        url = Transforms.getAbsoluteUri(url, basePath);
      }
  
      initPromise = Transforms.Resource.fetchJson(url).then(function (json) {
        ApproximateTerrainHeights._terrainHeights = json;
      });
      ApproximateTerrainHeights._initPromise = initPromise;
  
      return initPromise;
    };
  
    /**
     * Computes the minimum and maximum terrain heights for a given rectangle
     * @param {Rectangle} rectangle The bounding rectangle
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
     * @return {{minimumTerrainHeight: Number, maximumTerrainHeight: Number}}
     */
    ApproximateTerrainHeights.getMinimumMaximumHeights = function (
      rectangle,
      ellipsoid
    ) {
      //>>includeStart('debug', pragmas.debug);
      Check.Check.defined("rectangle", rectangle);
      if (!when.defined(ApproximateTerrainHeights._terrainHeights)) {
        throw new Check.DeveloperError(
          "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
        );
      }
      //>>includeEnd('debug');
      ellipsoid = when.defaultValue(ellipsoid, Cartesian2.Ellipsoid.WGS84);
  
      var xyLevel = getTileXYLevel(rectangle);
  
      // Get the terrain min/max for that tile
      var minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
      var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
      if (when.defined(xyLevel)) {
        var key = xyLevel.level + "-" + xyLevel.x + "-" + xyLevel.y;
        var heights = ApproximateTerrainHeights._terrainHeights[key];
        if (when.defined(heights)) {
          minTerrainHeight = heights[0];
          maxTerrainHeight = heights[1];
        }
  
        // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
        ellipsoid.cartographicToCartesian(
          Cartesian2.Rectangle.northeast(rectangle, scratchDiagonalCartographic),
          scratchDiagonalCartesianNE
        );
        ellipsoid.cartographicToCartesian(
          Cartesian2.Rectangle.southwest(rectangle, scratchDiagonalCartographic),
          scratchDiagonalCartesianSW
        );
  
        Cartesian2.Cartesian3.midpoint(
          scratchDiagonalCartesianSW,
          scratchDiagonalCartesianNE,
          scratchCenterCartesian
        );
        var surfacePosition = ellipsoid.scaleToGeodeticSurface(
          scratchCenterCartesian,
          scratchSurfaceCartesian
        );
        if (when.defined(surfacePosition)) {
          var distance = Cartesian2.Cartesian3.distance(
            scratchCenterCartesian,
            surfacePosition
          );
          minTerrainHeight = Math.min(minTerrainHeight, -distance);
        } else {
          minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
        }
      }
  
      minTerrainHeight = Math.max(
        ApproximateTerrainHeights._defaultMinTerrainHeight,
        minTerrainHeight
      );
  
      return {
        minimumTerrainHeight: minTerrainHeight,
        maximumTerrainHeight: maxTerrainHeight,
      };
    };
  
    /**
     * Computes the bounding sphere based on the tile heights in the rectangle
     * @param {Rectangle} rectangle The bounding rectangle
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
     * @return {BoundingSphere} The result bounding sphere
     */
    ApproximateTerrainHeights.getBoundingSphere = function (rectangle, ellipsoid) {
      //>>includeStart('debug', pragmas.debug);
      Check.Check.defined("rectangle", rectangle);
      if (!when.defined(ApproximateTerrainHeights._terrainHeights)) {
        throw new Check.DeveloperError(
          "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
        );
      }
      //>>includeEnd('debug');
      ellipsoid = when.defaultValue(ellipsoid, Cartesian2.Ellipsoid.WGS84);
  
      var xyLevel = getTileXYLevel(rectangle);
  
      // Get the terrain max for that tile
      var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
      if (when.defined(xyLevel)) {
        var key = xyLevel.level + "-" + xyLevel.x + "-" + xyLevel.y;
        var heights = ApproximateTerrainHeights._terrainHeights[key];
        if (when.defined(heights)) {
          maxTerrainHeight = heights[1];
        }
      }
  
      var result = Transforms.BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
      Transforms.BoundingSphere.fromRectangle3D(
        rectangle,
        ellipsoid,
        maxTerrainHeight,
        scratchBoundingSphere
      );
  
      return Transforms.BoundingSphere.union(result, scratchBoundingSphere, result);
    };
  
    function getTileXYLevel(rectangle) {
      Cartesian2.Cartographic.fromRadians(
        rectangle.east,
        rectangle.north,
        0.0,
        scratchCorners[0]
      );
      Cartesian2.Cartographic.fromRadians(
        rectangle.west,
        rectangle.north,
        0.0,
        scratchCorners[1]
      );
      Cartesian2.Cartographic.fromRadians(
        rectangle.east,
        rectangle.south,
        0.0,
        scratchCorners[2]
      );
      Cartesian2.Cartographic.fromRadians(
        rectangle.west,
        rectangle.south,
        0.0,
        scratchCorners[3]
      );
  
      // Determine which tile the bounding rectangle is in
      var lastLevelX = 0,
        lastLevelY = 0;
      var currentX = 0,
        currentY = 0;
      var maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;
      var i;
      for (i = 0; i <= maxLevel; ++i) {
        var failed = false;
        for (var j = 0; j < 4; ++j) {
          var corner = scratchCorners[j];
          tilingScheme.positionToTileXY(corner, i, scratchTileXY);
          if (j === 0) {
            currentX = scratchTileXY.x;
            currentY = scratchTileXY.y;
          } else if (currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
            failed = true;
            break;
          }
        }
  
        if (failed) {
          break;
        }
  
        lastLevelX = currentX;
        lastLevelY = currentY;
      }
  
      if (i === 0) {
        return undefined;
      }
  
      return {
        x: lastLevelX,
        y: lastLevelY,
        level: i > maxLevel ? maxLevel : i - 1,
      };
    }
  
    ApproximateTerrainHeights._terrainHeightsMaxLevel = 6;
    ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0;
    ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0;
    ApproximateTerrainHeights._terrainHeights = undefined;
    ApproximateTerrainHeights._initPromise = undefined;
  
    Object.defineProperties(ApproximateTerrainHeights, {
      /**
       * Determines if the terrain heights are initialized and ready to use. To initialize the terrain heights,
       * call {@link ApproximateTerrainHeights#initialize} and wait for the returned promise to resolve.
       * @type {Boolean}
       * @readonly
       * @memberof ApproximateTerrainHeights
       */
      initialized: {
        get: function () {
          return when.defined(ApproximateTerrainHeights._terrainHeights);
        },
      },
    });
  
    var PROJECTIONS = [Transforms.GeographicProjection, WebMercatorProjection.WebMercatorProjection];
    var PROJECTION_COUNT = PROJECTIONS.length;
  
    var MITER_BREAK_SMALL = Math.cos(_Math.CesiumMath.toRadians(30.0));
    var MITER_BREAK_LARGE = Math.cos(_Math.CesiumMath.toRadians(150.0));
  
    // Initial heights for constructing the wall.
    // Keeping WALL_INITIAL_MIN_HEIGHT near the ellipsoid surface helps
    // prevent precision problems with planes in the shader.
    // Putting the start point of a plane at ApproximateTerrainHeights._defaultMinTerrainHeight,
    // which is a highly conservative bound, usually puts the plane origin several thousands
    // of meters away from the actual terrain, causing floating point problems when checking
    // fragments on terrain against the plane.
    // Ellipsoid height is generally much closer.
    // The initial max height is arbitrary.
    // Both heights are corrected using ApproximateTerrainHeights for computing the actual volume geometry.
    var WALL_INITIAL_MIN_HEIGHT = 0.0;
    var WALL_INITIAL_MAX_HEIGHT = 1000.0;
  
    /**
     * A description of a polyline on terrain or 3D Tiles. Only to be used with {@link GroundPolylinePrimitive}.
     *
     * @alias GroundPolylineGeometry
     * @constructor
     *
     * @param {Object} options Options with the following properties:
     * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the polyline's points. Heights above the ellipsoid will be ignored.
     * @param {Number} [options.width=1.0] The screen space width in pixels.
     * @param {Number} [options.granularity=9999.0] The distance interval in meters used for interpolating options.points. Defaults to 9999.0 meters. Zero indicates no interpolation.
     * @param {Boolean} [options.loop=false] Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
     * @param {ArcType} [options.arcType=ArcType.GEODESIC] The type of line the polyline segments must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
     *
     * @exception {DeveloperError} At least two positions are required.
     *
     * @see GroundPolylinePrimitive
     *
     * @example
     * var positions = Cesium.Cartesian3.fromDegreesArray([
     *   -112.1340164450331, 36.05494287836128,
     *   -112.08821010582645, 36.097804071380715,
     *   -112.13296079730024, 36.168769146801104
     * ]);
     *
     * var geometry = new Cesium.GroundPolylineGeometry({
     *   positions : positions
     * });
     */
    function GroundPolylineGeometry(options) {
      options = when.defaultValue(options, when.defaultValue.EMPTY_OBJECT);
      var positions = options.positions;
  
      //>>includeStart('debug', pragmas.debug);
      if (!when.defined(positions) || positions.length < 2) {
        throw new Check.DeveloperError("At least two positions are required.");
      }
      if (
        when.defined(options.arcType) &&
        options.arcType !== ArcType.ArcType.GEODESIC &&
        options.arcType !== ArcType.ArcType.RHUMB
      ) {
        throw new Check.DeveloperError(
          "Valid options for arcType are ArcType.GEODESIC and ArcType.RHUMB."
        );
      }
      //>>includeEnd('debug');
  
      /**
       * The screen space width in pixels.
       * @type {Number}
       */
      this.width = when.defaultValue(options.width, 1.0); // Doesn't get packed, not necessary for computing geometry.
  
      this._positions = positions;
  
      /**
       * The distance interval used for interpolating options.points. Zero indicates no interpolation.
       * Default of 9999.0 allows centimeter accuracy with 32 bit floating point.
       * @type {Boolean}
       * @default 9999.0
       */
      this.granularity = when.defaultValue(options.granularity, 9999.0);
  
      /**
       * Whether during geometry creation a line segment will be added between the last and first line positions to make this Polyline a loop.
       * If the geometry has two positions this parameter will be ignored.
       * @type {Boolean}
       * @default false
       */
      this.loop = when.defaultValue(options.loop, false);
  
      /**
       * The type of path the polyline must follow. Valid options are {@link ArcType.GEODESIC} and {@link ArcType.RHUMB}.
       * @type {ArcType}
       * @default ArcType.GEODESIC
       */
      this.arcType = when.defaultValue(options.arcType, ArcType.ArcType.GEODESIC);
  
      this._ellipsoid = Cartesian2.Ellipsoid.WGS84;
  
      // MapProjections can't be packed, so store the index to a known MapProjection.
      this._projectionIndex = 0;
      this._workerName = "createGroundPolylineGeometry";
  
      // Used by GroundPolylinePrimitive to signal worker that scenemode is 3D only.
      this._scene3DOnly = false;
    }
  
    Object.defineProperties(GroundPolylineGeometry.prototype, {
      /**
       * The number of elements used to pack the object into an array.
       * @memberof GroundPolylineGeometry.prototype
       * @type {Number}
       * @readonly
       * @private
       */
      packedLength: {
        get: function () {
          return (
            1.0 +
            this._positions.length * 3 +
            1.0 +
            1.0 +
            1.0 +
            Cartesian2.Ellipsoid.packedLength +
            1.0 +
            1.0
          );
        },
      },
    });
  
    /**
     * Set the GroundPolylineGeometry's projection and ellipsoid.
     * Used by GroundPolylinePrimitive to signal scene information to the geometry for generating 2D attributes.
     *
     * @param {GroundPolylineGeometry} groundPolylineGeometry GroundPolylinGeometry describing a polyline on terrain or 3D Tiles.
     * @param {Projection} mapProjection A MapProjection used for projecting cartographic coordinates to 2D.
     * @private
     */
    GroundPolylineGeometry.setProjectionAndEllipsoid = function (
      groundPolylineGeometry,
      mapProjection
    ) {
      var projectionIndex = 0;
      for (var i = 0; i < PROJECTION_COUNT; i++) {
        if (mapProjection instanceof PROJECTIONS[i]) {
          projectionIndex = i;
          break;
        }
      }
  
      groundPolylineGeometry._projectionIndex = projectionIndex;
      groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;
    };
  
    var cart3Scratch1 = new Cartesian2.Cartesian3();
    var cart3Scratch2 = new Cartesian2.Cartesian3();
    var cart3Scratch3 = new Cartesian2.Cartesian3();
    function computeRightNormal(start, end, maxHeight, ellipsoid, result) {
      var startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);
      var startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);
      var endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);
  
      var up = direction(startTop, startBottom, cart3Scratch2);
      var forward = direction(endBottom, startBottom, cart3Scratch3);
  
      Cartesian2.Cartesian3.cross(forward, up, result);
      return Cartesian2.Cartesian3.normalize(result, result);
    }
  
    var interpolatedCartographicScratch = new Cartesian2.Cartographic();
    var interpolatedBottomScratch = new Cartesian2.Cartesian3();
    var interpolatedTopScratch = new Cartesian2.Cartesian3();
    var interpolatedNormalScratch = new Cartesian2.Cartesian3();
    function interpolateSegment(
      start,
      end,
      minHeight,
      maxHeight,
      granularity,
      arcType,
      ellipsoid,
      normalsArray,
      bottomPositionsArray,
      topPositionsArray,
      cartographicsArray
    ) {
      if (granularity === 0.0) {
        return;
      }
  
      var ellipsoidLine;
      if (arcType === ArcType.ArcType.GEODESIC) {
        ellipsoidLine = new EllipsoidGeodesic.EllipsoidGeodesic(start, end, ellipsoid);
      } else if (arcType === ArcType.ArcType.RHUMB) {
        ellipsoidLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(start, end, ellipsoid);
      }
  
      var surfaceDistance = ellipsoidLine.surfaceDistance;
      if (surfaceDistance < granularity) {
        return;
      }
  
      // Compute rightwards normal applicable at all interpolated points
      var interpolatedNormal = computeRightNormal(
        start,
        end,
        maxHeight,
        ellipsoid,
        interpolatedNormalScratch
      );
  
      var segments = Math.ceil(surfaceDistance / granularity);
      var interpointDistance = surfaceDistance / segments;
      var distanceFromStart = interpointDistance;
      var pointsToAdd = segments - 1;
      var packIndex = normalsArray.length;
      for (var i = 0; i < pointsToAdd; i++) {
        var interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(
          distanceFromStart,
          interpolatedCartographicScratch
        );
        var interpolatedBottom = getPosition(
          ellipsoid,
          interpolatedCartographic,
          minHeight,
          interpolatedBottomScratch
        );
        var interpolatedTop = getPosition(
          ellipsoid,
          interpolatedCartographic,
          maxHeight,
          interpolatedTopScratch
        );
  
        Cartesian2.Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);
        Cartesian2.Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);
        Cartesian2.Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);
        cartographicsArray.push(interpolatedCartographic.latitude);
        cartographicsArray.push(interpolatedCartographic.longitude);
  
        packIndex += 3;
        distanceFromStart += interpointDistance;
      }
    }
  
    var heightlessCartographicScratch = new Cartesian2.Cartographic();
    function getPosition(ellipsoid, cartographic, height, result) {
      Cartesian2.Cartographic.clone(cartographic, heightlessCartographicScratch);
      heightlessCartographicScratch.height = height;
      return Cartesian2.Cartographic.toCartesian(
        heightlessCartographicScratch,
        ellipsoid,
        result
      );
    }
  
    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolygonGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    GroundPolylineGeometry.pack = function (value, array, startingIndex) {
      //>>includeStart('debug', pragmas.debug);
      Check.Check.typeOf.object("value", value);
      Check.Check.defined("array", array);
      //>>includeEnd('debug');
  
      var index = when.defaultValue(startingIndex, 0);
  
      var positions = value._positions;
      var positionsLength = positions.length;
  
      array[index++] = positionsLength;
  
      for (var i = 0; i < positionsLength; ++i) {
        var cartesian = positions[i];
        Cartesian2.Cartesian3.pack(cartesian, array, index);
        index += 3;
      }
  
      array[index++] = value.granularity;
      array[index++] = value.loop ? 1.0 : 0.0;
      array[index++] = value.arcType;
  
      Cartesian2.Ellipsoid.pack(value._ellipsoid, array, index);
      index += Cartesian2.Ellipsoid.packedLength;
  
      array[index++] = value._projectionIndex;
      array[index++] = value._scene3DOnly ? 1.0 : 0.0;
  
      return array;
    };
  
    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolygonGeometry} [result] The object into which to store the result.
     */
    GroundPolylineGeometry.unpack = function (array, startingIndex, result) {
      //>>includeStart('debug', pragmas.debug);
      Check.Check.defined("array", array);
      //>>includeEnd('debug');
  
      var index = when.defaultValue(startingIndex, 0);
      var positionsLength = array[index++];
      var positions = new Array(positionsLength);
  
      for (var i = 0; i < positionsLength; i++) {
        positions[i] = Cartesian2.Cartesian3.unpack(array, index);
        index += 3;
      }
  
      var granularity = array[index++];
      var loop = array[index++] === 1.0;
      var arcType = array[index++];
  
      var ellipsoid = Cartesian2.Ellipsoid.unpack(array, index);
      index += Cartesian2.Ellipsoid.packedLength;
  
      var projectionIndex = array[index++];
      var scene3DOnly = array[index++] === 1.0;
  
      if (!when.defined(result)) {
        result = new GroundPolylineGeometry({
          positions: positions,
        });
      }
  
      result._positions = positions;
      result.granularity = granularity;
      result.loop = loop;
      result.arcType = arcType;
      result._ellipsoid = ellipsoid;
      result._projectionIndex = projectionIndex;
      result._scene3DOnly = scene3DOnly;
  
      return result;
    };
  
    function direction(target, origin, result) {
      Cartesian2.Cartesian3.subtract(target, origin, result);
      Cartesian2.Cartesian3.normalize(result, result);
      return result;
    }
  
    function tangentDirection(target, origin, up, result) {
      result = direction(target, origin, result);
  
      // orthogonalize
      result = Cartesian2.Cartesian3.cross(result, up, result);
      result = Cartesian2.Cartesian3.normalize(result, result);
      result = Cartesian2.Cartesian3.cross(up, result, result);
      return result;
    }
  
    var toPreviousScratch = new Cartesian2.Cartesian3();
    var toNextScratch = new Cartesian2.Cartesian3();
    var forwardScratch = new Cartesian2.Cartesian3();
    var vertexUpScratch = new Cartesian2.Cartesian3();
    var cosine90 = 0.0;
    var cosine180 = -1.0;
    function computeVertexMiterNormal(
      previousBottom,
      vertexBottom,
      vertexTop,
      nextBottom,
      result
    ) {
      var up = direction(vertexTop, vertexBottom, vertexUpScratch);
  
      // Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid
      var toPrevious = tangentDirection(
        previousBottom,
        vertexBottom,
        up,
        toPreviousScratch
      );
      var toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);
  
      // Check if tangents are almost opposite - if so, no need to miter.
      if (
        _Math.CesiumMath.equalsEpsilon(
          Cartesian2.Cartesian3.dot(toPrevious, toNext),
          cosine180,
          _Math.CesiumMath.EPSILON5
        )
      ) {
        result = Cartesian2.Cartesian3.cross(up, toPrevious, result);
        result = Cartesian2.Cartesian3.normalize(result, result);
        return result;
      }
  
      // Average directions to previous and to next in the plane of Up
      result = Cartesian2.Cartesian3.add(toNext, toPrevious, result);
      result = Cartesian2.Cartesian3.normalize(result, result);
  
      // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")
      var forward = Cartesian2.Cartesian3.cross(up, result, forwardScratch);
      if (Cartesian2.Cartesian3.dot(toNext, forward) < cosine90) {
        result = Cartesian2.Cartesian3.negate(result, result);
      }
  
      return result;
    }
  
    var XZ_PLANE = Plane.Plane.fromPointNormal(Cartesian2.Cartesian3.ZERO, Cartesian2.Cartesian3.UNIT_Y);
  
    var previousBottomScratch = new Cartesian2.Cartesian3();
    var vertexBottomScratch = new Cartesian2.Cartesian3();
    var vertexTopScratch = new Cartesian2.Cartesian3();
    var nextBottomScratch = new Cartesian2.Cartesian3();
    var vertexNormalScratch = new Cartesian2.Cartesian3();
    var intersectionScratch = new Cartesian2.Cartesian3();
    var cartographicScratch0 = new Cartesian2.Cartographic();
    var cartographicScratch1 = new Cartesian2.Cartographic();
    var cartographicIntersectionScratch = new Cartesian2.Cartographic();
    /**
     * Computes shadow volumes for the ground polyline, consisting of its vertices, indices, and a bounding sphere.
     * Vertices are "fat," packing all the data needed in each volume to describe a line on terrain or 3D Tiles.
     * Should not be called independent of {@link GroundPolylinePrimitive}.
     *
     * @param {GroundPolylineGeometry} groundPolylineGeometry
     * @private
     */
    GroundPolylineGeometry.createGeometry = function (groundPolylineGeometry) {
      var compute2dAttributes = !groundPolylineGeometry._scene3DOnly;
      var loop = groundPolylineGeometry.loop;
      var ellipsoid = groundPolylineGeometry._ellipsoid;
      var granularity = groundPolylineGeometry.granularity;
      var arcType = groundPolylineGeometry.arcType;
      var projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](
        ellipsoid
      );
  
      var minHeight = WALL_INITIAL_MIN_HEIGHT;
      var maxHeight = WALL_INITIAL_MAX_HEIGHT;
  
      var index;
      var i;
  
      var positions = groundPolylineGeometry._positions;
      var positionsLength = positions.length;
  
      if (positionsLength === 2) {
        loop = false;
      }
  
      // Split positions across the IDL and the Prime Meridian as well.
      // Split across prime meridian because very large geometries crossing the Prime Meridian but not the IDL
      // may get split by the plane of IDL + Prime Meridian.
      var p0;
      var p1;
      var c0;
      var c1;
      var rhumbLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(undefined, undefined, ellipsoid);
      var intersection;
      var intersectionCartographic;
      var intersectionLongitude;
      var splitPositions = [positions[0]];
      for (i = 0; i < positionsLength - 1; i++) {
        p0 = positions[i];
        p1 = positions[i + 1];
        intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(
          p0,
          p1,
          XZ_PLANE,
          intersectionScratch
        );
        if (
          when.defined(intersection) &&
          !Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
          !Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)
        ) {
          if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
            splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
          } else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
            intersectionLongitude = ellipsoid.cartesianToCartographic(
              intersection,
              cartographicScratch0
            ).longitude;
            c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
            c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
            rhumbLine.setEndPoints(c0, c1);
            intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
              intersectionLongitude,
              cartographicIntersectionScratch
            );
            intersection = ellipsoid.cartographicToCartesian(
              intersectionCartographic,
              intersectionScratch
            );
            if (
              when.defined(intersection) &&
              !Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
              !Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)
            ) {
              splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
            }
          }
        }
        splitPositions.push(p1);
      }
  
      if (loop) {
        p0 = positions[positionsLength - 1];
        p1 = positions[0];
        intersection = IntersectionTests.IntersectionTests.lineSegmentPlane(
          p0,
          p1,
          XZ_PLANE,
          intersectionScratch
        );
        if (
          when.defined(intersection) &&
          !Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
          !Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)
        ) {
          if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {
            splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
          } else if (groundPolylineGeometry.arcType === ArcType.ArcType.RHUMB) {
            intersectionLongitude = ellipsoid.cartesianToCartographic(
              intersection,
              cartographicScratch0
            ).longitude;
            c0 = ellipsoid.cartesianToCartographic(p0, cartographicScratch0);
            c1 = ellipsoid.cartesianToCartographic(p1, cartographicScratch1);
            rhumbLine.setEndPoints(c0, c1);
            intersectionCartographic = rhumbLine.findIntersectionWithLongitude(
              intersectionLongitude,
              cartographicIntersectionScratch
            );
            intersection = ellipsoid.cartographicToCartesian(
              intersectionCartographic,
              intersectionScratch
            );
            if (
              when.defined(intersection) &&
              !Cartesian2.Cartesian3.equalsEpsilon(intersection, p0, _Math.CesiumMath.EPSILON7) &&
              !Cartesian2.Cartesian3.equalsEpsilon(intersection, p1, _Math.CesiumMath.EPSILON7)
            ) {
              splitPositions.push(Cartesian2.Cartesian3.clone(intersection));
            }
          }
        }
      }
      var cartographicsLength = splitPositions.length;
  
      var cartographics = new Array(cartographicsLength);
      for (i = 0; i < cartographicsLength; i++) {
        var cartographic = Cartesian2.Cartographic.fromCartesian(splitPositions[i], ellipsoid);
        cartographic.height = 0.0;
        cartographics[i] = cartographic;
      }
  
      cartographics = arrayRemoveDuplicates.arrayRemoveDuplicates(
        cartographics,
        Cartesian2.Cartographic.equalsEpsilon
      );
      cartographicsLength = cartographics.length;
  
      if (cartographicsLength < 2) {
        return undefined;
      }
  
      /**** Build heap-side arrays for positions, interpolated cartographics, and normals from which to compute vertices ****/
      // We build a "wall" and then decompose it into separately connected component "volumes" because we need a lot
      // of information about the wall. Also, this simplifies interpolation.
      // Convention: "next" and "end" are locally forward to each segment of the wall,
      // and we are computing normals pointing towards the local right side of the vertices in each segment.
      var cartographicsArray = [];
      var normalsArray = [];
      var bottomPositionsArray = [];
      var topPositionsArray = [];
  
      var previousBottom = previousBottomScratch;
      var vertexBottom = vertexBottomScratch;
      var vertexTop = vertexTopScratch;
      var nextBottom = nextBottomScratch;
      var vertexNormal = vertexNormalScratch;
  
      // First point - either loop or attach a "perpendicular" normal
      var startCartographic = cartographics[0];
      var nextCartographic = cartographics[1];
  
      var prestartCartographic = cartographics[cartographicsLength - 1];
      previousBottom = getPosition(
        ellipsoid,
        prestartCartographic,
        minHeight,
        previousBottom
      );
      nextBottom = getPosition(ellipsoid, nextCartographic, minHeight, nextBottom);
      vertexBottom = getPosition(
        ellipsoid,
        startCartographic,
        minHeight,
        vertexBottom
      );
      vertexTop = getPosition(ellipsoid, startCartographic, maxHeight, vertexTop);
  
      if (loop) {
        vertexNormal = computeVertexMiterNormal(
          previousBottom,
          vertexBottom,
          vertexTop,
          nextBottom,
          vertexNormal
        );
      } else {
        vertexNormal = computeRightNormal(
          startCartographic,
          nextCartographic,
          maxHeight,
          ellipsoid,
          vertexNormal
        );
      }
  
      Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, 0);
      Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);
      Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, 0);
      cartographicsArray.push(startCartographic.latitude);
      cartographicsArray.push(startCartographic.longitude);
  
      interpolateSegment(
        startCartographic,
        nextCartographic,
        minHeight,
        maxHeight,
        granularity,
        arcType,
        ellipsoid,
        normalsArray,
        bottomPositionsArray,
        topPositionsArray,
        cartographicsArray
      );
  
      // All inbetween points
      for (i = 1; i < cartographicsLength - 1; ++i) {
        previousBottom = Cartesian2.Cartesian3.clone(vertexBottom, previousBottom);
        vertexBottom = Cartesian2.Cartesian3.clone(nextBottom, vertexBottom);
        var vertexCartographic = cartographics[i];
        getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);
        getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);
  
        computeVertexMiterNormal(
          previousBottom,
          vertexBottom,
          vertexTop,
          nextBottom,
          vertexNormal
        );
  
        index = normalsArray.length;
        Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, index);
        Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
        Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, index);
        cartographicsArray.push(vertexCartographic.latitude);
        cartographicsArray.push(vertexCartographic.longitude);
  
        interpolateSegment(
          cartographics[i],
          cartographics[i + 1],
          minHeight,
          maxHeight,
          granularity,
          arcType,
          ellipsoid,
          normalsArray,
          bottomPositionsArray,
          topPositionsArray,
          cartographicsArray
        );
      }
  
      // Last point - either loop or attach a normal "perpendicular" to the wall.
      var endCartographic = cartographics[cartographicsLength - 1];
      var preEndCartographic = cartographics[cartographicsLength - 2];
  
      vertexBottom = getPosition(
        ellipsoid,
        endCartographic,
        minHeight,
        vertexBottom
      );
      vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);
  
      if (loop) {
        var postEndCartographic = cartographics[0];
        previousBottom = getPosition(
          ellipsoid,
          preEndCartographic,
          minHeight,
          previousBottom
        );
        nextBottom = getPosition(
          ellipsoid,
          postEndCartographic,
          minHeight,
          nextBottom
        );
  
        vertexNormal = computeVertexMiterNormal(
          previousBottom,
          vertexBottom,
          vertexTop,
          nextBottom,
          vertexNormal
        );
      } else {
        vertexNormal = computeRightNormal(
          preEndCartographic,
          endCartographic,
          maxHeight,
          ellipsoid,
          vertexNormal
        );
      }
  
      index = normalsArray.length;
      Cartesian2.Cartesian3.pack(vertexNormal, normalsArray, index);
      Cartesian2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);
      Cartesian2.Cartesian3.pack(vertexTop, topPositionsArray, index);
      cartographicsArray.push(endCartographic.latitude);
      cartographicsArray.push(endCartographic.longitude);
  
      if (loop) {
        interpolateSegment(
          endCartographic,
          startCartographic,
          minHeight,
          maxHeight,
          granularity,
          arcType,
          ellipsoid,
          normalsArray,
          bottomPositionsArray,
          topPositionsArray,
          cartographicsArray
        );
        index = normalsArray.length;
        for (i = 0; i < 3; ++i) {
          normalsArray[index + i] = normalsArray[i];
          bottomPositionsArray[index + i] = bottomPositionsArray[i];
          topPositionsArray[index + i] = topPositionsArray[i];
        }
        cartographicsArray.push(startCartographic.latitude);
        cartographicsArray.push(startCartographic.longitude);
      }
  
      return generateGeometryAttributes(
        loop,
        projection,
        bottomPositionsArray,
        topPositionsArray,
        normalsArray,
        cartographicsArray,
        compute2dAttributes
      );
    };
  
    // If the end normal angle is too steep compared to the direction of the line segment,
    // "break" the miter by rotating the normal 90 degrees around the "up" direction at the point
    // For ultra precision we would want to project into a plane, but in practice this is sufficient.
    var lineDirectionScratch = new Cartesian2.Cartesian3();
    var matrix3Scratch = new Transforms.Matrix3();
    var quaternionScratch = new Transforms.Quaternion();
    function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {
      var lineDirection = direction(endBottom, startBottom, lineDirectionScratch);
  
      var dot = Cartesian2.Cartesian3.dot(lineDirection, endGeometryNormal);
      if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {
        var vertexUp = direction(endTop, endBottom, vertexUpScratch);
        var angle =
          dot < MITER_BREAK_LARGE
            ? _Math.CesiumMath.PI_OVER_TWO
            : -_Math.CesiumMath.PI_OVER_TWO;
        var quaternion = Transforms.Quaternion.fromAxisAngle(
          vertexUp,
          angle,
          quaternionScratch
        );
        var rotationMatrix = Transforms.Matrix3.fromQuaternion(quaternion, matrix3Scratch);
        Transforms.Matrix3.multiplyByVector(
          rotationMatrix,
          endGeometryNormal,
          endGeometryNormal
        );
        return true;
      }
      return false;
    }
  
    var endPosCartographicScratch = new Cartesian2.Cartographic();
    var normalStartpointScratch = new Cartesian2.Cartesian3();
    var normalEndpointScratch = new Cartesian2.Cartesian3();
    function projectNormal(
      projection,
      cartographic,
      normal,
      projectedPosition,
      result
    ) {
      var position = Cartesian2.Cartographic.toCartesian(
        cartographic,
        projection._ellipsoid,
        normalStartpointScratch
      );
      var normalEndpoint = Cartesian2.Cartesian3.add(position, normal, normalEndpointScratch);
      var flipNormal = false;
  
      var ellipsoid = projection._ellipsoid;
      var normalEndpointCartographic = ellipsoid.cartesianToCartographic(
        normalEndpoint,
        endPosCartographicScratch
      );
      // If normal crosses the IDL, go the other way and flip the result.
      // In practice this almost never happens because the cartographic start
      // and end points of each segment are "nudged" to be on the same side
      // of the IDL and slightly away from the IDL.
      if (
        Math.abs(cartographic.longitude - normalEndpointCartographic.longitude) >
        _Math.CesiumMath.PI_OVER_TWO
      ) {
        flipNormal = true;
        normalEndpoint = Cartesian2.Cartesian3.subtract(
          position,
          normal,
          normalEndpointScratch
        );
        normalEndpointCartographic = ellipsoid.cartesianToCartographic(
          normalEndpoint,
          endPosCartographicScratch
        );
      }
  
      normalEndpointCartographic.height = 0.0;
      var normalEndpointProjected = projection.project(
        normalEndpointCartographic,
        result
      );
      result = Cartesian2.Cartesian3.subtract(
        normalEndpointProjected,
        projectedPosition,
        result
      );
      result.z = 0.0;
      result = Cartesian2.Cartesian3.normalize(result, result);
      if (flipNormal) {
        Cartesian2.Cartesian3.negate(result, result);
      }
      return result;
    }
  
    var adjustHeightNormalScratch = new Cartesian2.Cartesian3();
    var adjustHeightOffsetScratch = new Cartesian2.Cartesian3();
    function adjustHeights(
      bottom,
      top,
      minHeight,
      maxHeight,
      adjustHeightBottom,
      adjustHeightTop
    ) {
      // bottom and top should be at WALL_INITIAL_MIN_HEIGHT and WALL_INITIAL_MAX_HEIGHT, respectively
      var adjustHeightNormal = Cartesian2.Cartesian3.subtract(
        top,
        bottom,
        adjustHeightNormalScratch
      );
      Cartesian2.Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);
  
      var distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;
      var adjustHeightOffset = Cartesian2.Cartesian3.multiplyByScalar(
        adjustHeightNormal,
        distanceForBottom,
        adjustHeightOffsetScratch
      );
      Cartesian2.Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);
  
      var distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;
      adjustHeightOffset = Cartesian2.Cartesian3.multiplyByScalar(
        adjustHeightNormal,
        distanceForTop,
        adjustHeightOffsetScratch
      );
      Cartesian2.Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);
    }
  
    var nudgeDirectionScratch = new Cartesian2.Cartesian3();
    function nudgeXZ(start, end) {
      var startToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, start);
      var endToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, end);
      var offset = nudgeDirectionScratch;
      // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space
      if (_Math.CesiumMath.equalsEpsilon(startToXZdistance, 0.0, _Math.CesiumMath.EPSILON2)) {
        offset = direction(end, start, offset);
        Cartesian2.Cartesian3.multiplyByScalar(offset, _Math.CesiumMath.EPSILON2, offset);
        Cartesian2.Cartesian3.add(start, offset, start);
      } else if (
        _Math.CesiumMath.equalsEpsilon(endToXZdistance, 0.0, _Math.CesiumMath.EPSILON2)
      ) {
        offset = direction(start, end, offset);
        Cartesian2.Cartesian3.multiplyByScalar(offset, _Math.CesiumMath.EPSILON2, offset);
        Cartesian2.Cartesian3.add(end, offset, end);
      }
    }
  
    // "Nudge" cartographic coordinates so start and end are on the same side of the IDL.
    // Nudge amounts are tiny, basically just an IDL flip.
    // Only used for 2D/CV.
    function nudgeCartographic(start, end) {
      var absStartLon = Math.abs(start.longitude);
      var absEndLon = Math.abs(end.longitude);
      if (
        _Math.CesiumMath.equalsEpsilon(absStartLon, _Math.CesiumMath.PI, _Math.CesiumMath.EPSILON11)
      ) {
        var endSign = _Math.CesiumMath.sign(end.longitude);
        start.longitude = endSign * (absStartLon - _Math.CesiumMath.EPSILON11);
        return 1;
      } else if (
        _Math.CesiumMath.equalsEpsilon(absEndLon, _Math.CesiumMath.PI, _Math.CesiumMath.EPSILON11)
      ) {
        var startSign = _Math.CesiumMath.sign(start.longitude);
        end.longitude = startSign * (absEndLon - _Math.CesiumMath.EPSILON11);
        return 2;
      }
      return 0;
    }
  
    var startCartographicScratch = new Cartesian2.Cartographic();
    var endCartographicScratch = new Cartesian2.Cartographic();
  
    var segmentStartTopScratch = new Cartesian2.Cartesian3();
    var segmentEndTopScratch = new Cartesian2.Cartesian3();
    var segmentStartBottomScratch = new Cartesian2.Cartesian3();
    var segmentEndBottomScratch = new Cartesian2.Cartesian3();
    var segmentStartNormalScratch = new Cartesian2.Cartesian3();
    var segmentEndNormalScratch = new Cartesian2.Cartesian3();
  
    var getHeightCartographics = [startCartographicScratch, endCartographicScratch];
    var getHeightRectangleScratch = new Cartesian2.Rectangle();
  
    var adjustHeightStartTopScratch = new Cartesian2.Cartesian3();
    var adjustHeightEndTopScratch = new Cartesian2.Cartesian3();
    var adjustHeightStartBottomScratch = new Cartesian2.Cartesian3();
    var adjustHeightEndBottomScratch = new Cartesian2.Cartesian3();
  
    var segmentStart2DScratch = new Cartesian2.Cartesian3();
    var segmentEnd2DScratch = new Cartesian2.Cartesian3();
    var segmentStartNormal2DScratch = new Cartesian2.Cartesian3();
    var segmentEndNormal2DScratch = new Cartesian2.Cartesian3();
  
    var offsetScratch = new Cartesian2.Cartesian3();
    var startUpScratch = new Cartesian2.Cartesian3();
    var endUpScratch = new Cartesian2.Cartesian3();
    var rightScratch = new Cartesian2.Cartesian3();
    var startPlaneNormalScratch = new Cartesian2.Cartesian3();
    var endPlaneNormalScratch = new Cartesian2.Cartesian3();
    var encodeScratch = new EncodedCartesian3.EncodedCartesian3();
  
    var encodeScratch2D = new EncodedCartesian3.EncodedCartesian3();
    var forwardOffset2DScratch = new Cartesian2.Cartesian3();
    var right2DScratch = new Cartesian2.Cartesian3();
  
    var normalNudgeScratch = new Cartesian2.Cartesian3();
  
    var scratchBoundingSpheres = [new Transforms.BoundingSphere(), new Transforms.BoundingSphere()];
  
    // Winding order is reversed so each segment's volume is inside-out
    var REFERENCE_INDICES = [
      0,
      2,
      1,
      0,
      3,
      2, // right
      0,
      7,
      3,
      0,
      4,
      7, // start
      0,
      5,
      4,
      0,
      1,
      5, // bottom
      5,
      7,
      4,
      5,
      6,
      7, // left
      5,
      2,
      6,
      5,
      1,
      2, // end
      3,
      6,
      2,
      3,
      7,
      6, // top
    ];
    var REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
  
    // Decompose the "wall" into a series of shadow volumes.
    // Each shadow volume's vertices encode a description of the line it contains,
    // including mitering planes at the end points, a plane along the line itself,
    // and attributes for computing length-wise texture coordinates.
    function generateGeometryAttributes(
      loop,
      projection,
      bottomPositionsArray,
      topPositionsArray,
      normalsArray,
      cartographicsArray,
      compute2dAttributes
    ) {
      var i;
      var index;
      var ellipsoid = projection._ellipsoid;
  
      // Each segment will have 8 vertices
      var segmentCount = bottomPositionsArray.length / 3 - 1;
      var vertexCount = segmentCount * 8;
      var arraySizeVec4 = vertexCount * 4;
      var indexCount = segmentCount * 36;
  
      var indices =
        vertexCount > 65535
          ? new Uint32Array(indexCount)
          : new Uint16Array(indexCount);
      var positionsArray = new Float64Array(vertexCount * 3);
  
      var startHiAndForwardOffsetX = new Float32Array(arraySizeVec4);
      var startLoAndForwardOffsetY = new Float32Array(arraySizeVec4);
      var startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4);
      var endNormalAndTextureCoordinateNormalizationX = new Float32Array(
        arraySizeVec4
      );
      var rightNormalAndTextureCoordinateNormalizationY = new Float32Array(
        arraySizeVec4
      );
  
      var startHiLo2D;
      var offsetAndRight2D;
      var startEndNormals2D;
      var texcoordNormalization2D;
  
      if (compute2dAttributes) {
        startHiLo2D = new Float32Array(arraySizeVec4);
        offsetAndRight2D = new Float32Array(arraySizeVec4);
        startEndNormals2D = new Float32Array(arraySizeVec4);
        texcoordNormalization2D = new Float32Array(vertexCount * 2);
      }
  
      /*** Compute total lengths for texture coordinate normalization ***/
      // 2D
      var cartographicsLength = cartographicsArray.length / 2;
      var length2D = 0.0;
  
      var startCartographic = startCartographicScratch;
      startCartographic.height = 0.0;
      var endCartographic = endCartographicScratch;
      endCartographic.height = 0.0;
  
      var segmentStartCartesian = segmentStartTopScratch;
      var segmentEndCartesian = segmentEndTopScratch;
  
      if (compute2dAttributes) {
        index = 0;
        for (i = 1; i < cartographicsLength; i++) {
          // Don't clone anything from previous segment b/c possible IDL touch
          startCartographic.latitude = cartographicsArray[index];
          startCartographic.longitude = cartographicsArray[index + 1];
          endCartographic.latitude = cartographicsArray[index + 2];
          endCartographic.longitude = cartographicsArray[index + 3];
  
          segmentStartCartesian = projection.project(
            startCartographic,
            segmentStartCartesian
          );
          segmentEndCartesian = projection.project(
            endCartographic,
            segmentEndCartesian
          );
          length2D += Cartesian2.Cartesian3.distance(
            segmentStartCartesian,
            segmentEndCartesian
          );
          index += 2;
        }
      }
  
      // 3D
      var positionsLength = topPositionsArray.length / 3;
      segmentEndCartesian = Cartesian2.Cartesian3.unpack(
        topPositionsArray,
        0,
        segmentEndCartesian
      );
      var length3D = 0.0;
  
      index = 3;
      for (i = 1; i < positionsLength; i++) {
        segmentStartCartesian = Cartesian2.Cartesian3.clone(
          segmentEndCartesian,
          segmentStartCartesian
        );
        segmentEndCartesian = Cartesian2.Cartesian3.unpack(
          topPositionsArray,
          index,
          segmentEndCartesian
        );
        length3D += Cartesian2.Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);
        index += 3;
      }
  
      /*** Generate segments ***/
      var j;
      index = 3;
      var cartographicsIndex = 0;
      var vec2sWriteIndex = 0;
      var vec3sWriteIndex = 0;
      var vec4sWriteIndex = 0;
      var miterBroken = false;
  
      var endBottom = Cartesian2.Cartesian3.unpack(
        bottomPositionsArray,
        0,
        segmentEndBottomScratch
      );
      var endTop = Cartesian2.Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);
      var endGeometryNormal = Cartesian2.Cartesian3.unpack(
        normalsArray,
        0,
        segmentEndNormalScratch
      );
  
      if (loop) {
        var preEndBottom = Cartesian2.Cartesian3.unpack(
          bottomPositionsArray,
          bottomPositionsArray.length - 6,
          segmentStartBottomScratch
        );
        if (breakMiter(endGeometryNormal, preEndBottom, endBottom, endTop)) {
          // Miter broken as if for the last point in the loop, needs to be inverted for first point (clone of endBottom)
          endGeometryNormal = Cartesian2.Cartesian3.negate(
            endGeometryNormal,
            endGeometryNormal
          );
        }
      }
  
      var lengthSoFar3D = 0.0;
      var lengthSoFar2D = 0.0;
  
      // For translating bounding volume
      var sumHeights = 0.0;
  
      for (i = 0; i < segmentCount; i++) {
        var startBottom = Cartesian2.Cartesian3.clone(endBottom, segmentStartBottomScratch);
        var startTop = Cartesian2.Cartesian3.clone(endTop, segmentStartTopScratch);
        var startGeometryNormal = Cartesian2.Cartesian3.clone(
          endGeometryNormal,
          segmentStartNormalScratch
        );
  
        if (miterBroken) {
          startGeometryNormal = Cartesian2.Cartesian3.negate(
            startGeometryNormal,
            startGeometryNormal
          );
        }
  
        endBottom = Cartesian2.Cartesian3.unpack(
          bottomPositionsArray,
          index,
          segmentEndBottomScratch
        );
        endTop = Cartesian2.Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);
        endGeometryNormal = Cartesian2.Cartesian3.unpack(
          normalsArray,
          index,
          segmentEndNormalScratch
        );
  
        miterBroken = breakMiter(endGeometryNormal, startBottom, endBottom, endTop);
  
        // 2D - don't clone anything from previous segment b/c possible IDL touch
        startCartographic.latitude = cartographicsArray[cartographicsIndex];
        startCartographic.longitude = cartographicsArray[cartographicsIndex + 1];
        endCartographic.latitude = cartographicsArray[cartographicsIndex + 2];
        endCartographic.longitude = cartographicsArray[cartographicsIndex + 3];
        var start2D;
        var end2D;
        var startGeometryNormal2D;
        var endGeometryNormal2D;
  
        if (compute2dAttributes) {
          var nudgeResult = nudgeCartographic(startCartographic, endCartographic);
          start2D = projection.project(startCartographic, segmentStart2DScratch);
          end2D = projection.project(endCartographic, segmentEnd2DScratch);
          var direction2D = direction(end2D, start2D, forwardOffset2DScratch);
          direction2D.y = Math.abs(direction2D.y);
  
          startGeometryNormal2D = segmentStartNormal2DScratch;
          endGeometryNormal2D = segmentEndNormal2DScratch;
          if (
            nudgeResult === 0 ||
            Cartesian2.Cartesian3.dot(direction2D, Cartesian2.Cartesian3.UNIT_Y) > MITER_BREAK_SMALL
          ) {
            // No nudge - project the original normal
            // Or, if the line's angle relative to the IDL is very acute,
            // in which case snapping will produce oddly shaped volumes.
            startGeometryNormal2D = projectNormal(
              projection,
              startCartographic,
              startGeometryNormal,
              start2D,
              segmentStartNormal2DScratch
            );
            endGeometryNormal2D = projectNormal(
              projection,
              endCartographic,
              endGeometryNormal,
              end2D,
              segmentEndNormal2DScratch
            );
          } else if (nudgeResult === 1) {
            // Start is close to IDL - snap start normal to align with IDL
            endGeometryNormal2D = projectNormal(
              projection,
              endCartographic,
              endGeometryNormal,
              end2D,
              segmentEndNormal2DScratch
            );
            startGeometryNormal2D.x = 0.0;
            // If start longitude is negative and end longitude is less negative, relative right is unit -Y
            // If start longitude is positive and end longitude is less positive, relative right is unit +Y
            startGeometryNormal2D.y = _Math.CesiumMath.sign(
              startCartographic.longitude - Math.abs(endCartographic.longitude)
            );
            startGeometryNormal2D.z = 0.0;
          } else {
            // End is close to IDL - snap end normal to align with IDL
            startGeometryNormal2D = projectNormal(
              projection,
              startCartographic,
              startGeometryNormal,
              start2D,
              segmentStartNormal2DScratch
            );
            endGeometryNormal2D.x = 0.0;
            // If end longitude is negative and start longitude is less negative, relative right is unit Y
            // If end longitude is positive and start longitude is less positive, relative right is unit -Y
            endGeometryNormal2D.y = _Math.CesiumMath.sign(
              startCartographic.longitude - endCartographic.longitude
            );
            endGeometryNormal2D.z = 0.0;
          }
        }
  
        /****************************************
         * Geometry descriptors of a "line on terrain,"
         * as opposed to the "shadow volume used to draw
         * the line on terrain":
         * - position of start + offset to end
         * - start, end, and right-facing planes
         * - encoded texture coordinate offsets
         ****************************************/
  
        /* 3D */
        var segmentLength3D = Cartesian2.Cartesian3.distance(startTop, endTop);
  
        var encodedStart = EncodedCartesian3.EncodedCartesian3.fromCartesian(
          startBottom,
          encodeScratch
        );
        var forwardOffset = Cartesian2.Cartesian3.subtract(
          endBottom,
          startBottom,
          offsetScratch
        );
        var forward = Cartesian2.Cartesian3.normalize(forwardOffset, rightScratch);
  
        var startUp = Cartesian2.Cartesian3.subtract(startTop, startBottom, startUpScratch);
        startUp = Cartesian2.Cartesian3.normalize(startUp, startUp);
        var rightNormal = Cartesian2.Cartesian3.cross(forward, startUp, rightScratch);
        rightNormal = Cartesian2.Cartesian3.normalize(rightNormal, rightNormal);
  
        var startPlaneNormal = Cartesian2.Cartesian3.cross(
          startUp,
          startGeometryNormal,
          startPlaneNormalScratch
        );
        startPlaneNormal = Cartesian2.Cartesian3.normalize(startPlaneNormal, startPlaneNormal);
  
        var endUp = Cartesian2.Cartesian3.subtract(endTop, endBottom, endUpScratch);
        endUp = Cartesian2.Cartesian3.normalize(endUp, endUp);
        var endPlaneNormal = Cartesian2.Cartesian3.cross(
          endGeometryNormal,
          endUp,
          endPlaneNormalScratch
        );
        endPlaneNormal = Cartesian2.Cartesian3.normalize(endPlaneNormal, endPlaneNormal);
  
        var texcoordNormalization3DX = segmentLength3D / length3D;
        var texcoordNormalization3DY = lengthSoFar3D / length3D;
  
        /* 2D */
        var segmentLength2D = 0.0;
        var encodedStart2D;
        var forwardOffset2D;
        var right2D;
        var texcoordNormalization2DX = 0.0;
        var texcoordNormalization2DY = 0.0;
        if (compute2dAttributes) {
          segmentLength2D = Cartesian2.Cartesian3.distance(start2D, end2D);
  
          encodedStart2D = EncodedCartesian3.EncodedCartesian3.fromCartesian(
            start2D,
            encodeScratch2D
          );
          forwardOffset2D = Cartesian2.Cartesian3.subtract(
            end2D,
            start2D,
            forwardOffset2DScratch
          );
  
          // Right direction is just forward direction rotated by -90 degrees around Z
          // Similarly with plane normals
          right2D = Cartesian2.Cartesian3.normalize(forwardOffset2D, right2DScratch);
          var swap = right2D.x;
          right2D.x = right2D.y;
          right2D.y = -swap;
  
          texcoordNormalization2DX = segmentLength2D / length2D;
          texcoordNormalization2DY = lengthSoFar2D / length2D;
        }
        /** Pack **/
        for (j = 0; j < 8; j++) {
          var vec4Index = vec4sWriteIndex + j * 4;
          var vec2Index = vec2sWriteIndex + j * 2;
          var wIndex = vec4Index + 3;
  
          // Encode sidedness of vertex relative to right plane in texture coordinate normalization X,
          // whether vertex is top or bottom of volume in sign/magnitude of normalization Y.
          var rightPlaneSide = j < 4 ? 1.0 : -1.0;
          var topBottomSide = j === 2 || j === 3 || j === 6 || j === 7 ? 1.0 : -1.0;
  
          // 3D
          Cartesian2.Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);
          startHiAndForwardOffsetX[wIndex] = forwardOffset.x;
  
          Cartesian2.Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);
          startLoAndForwardOffsetY[wIndex] = forwardOffset.y;
  
          Cartesian2.Cartesian3.pack(
            startPlaneNormal,
            startNormalAndForwardOffsetZ,
            vec4Index
          );
          startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;
  
          Cartesian2.Cartesian3.pack(
            endPlaneNormal,
            endNormalAndTextureCoordinateNormalizationX,
            vec4Index
          );
          endNormalAndTextureCoordinateNormalizationX[wIndex] =
            texcoordNormalization3DX * rightPlaneSide;
  
          Cartesian2.Cartesian3.pack(
            rightNormal,
            rightNormalAndTextureCoordinateNormalizationY,
            vec4Index
          );
  
          var texcoordNormalization = texcoordNormalization3DY * topBottomSide;
          if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
            texcoordNormalization = 9.0; // some value greater than 1.0
          }
          rightNormalAndTextureCoordinateNormalizationY[
            wIndex
          ] = texcoordNormalization;
  
          // 2D
          if (compute2dAttributes) {
            startHiLo2D[vec4Index] = encodedStart2D.high.x;
            startHiLo2D[vec4Index + 1] = encodedStart2D.high.y;
            startHiLo2D[vec4Index + 2] = encodedStart2D.low.x;
            startHiLo2D[vec4Index + 3] = encodedStart2D.low.y;
  
            startEndNormals2D[vec4Index] = -startGeometryNormal2D.y;
            startEndNormals2D[vec4Index + 1] = startGeometryNormal2D.x;
            startEndNormals2D[vec4Index + 2] = endGeometryNormal2D.y;
            startEndNormals2D[vec4Index + 3] = -endGeometryNormal2D.x;
  
            offsetAndRight2D[vec4Index] = forwardOffset2D.x;
            offsetAndRight2D[vec4Index + 1] = forwardOffset2D.y;
            offsetAndRight2D[vec4Index + 2] = right2D.x;
            offsetAndRight2D[vec4Index + 3] = right2D.y;
  
            texcoordNormalization2D[vec2Index] =
              texcoordNormalization2DX * rightPlaneSide;
  
            texcoordNormalization = texcoordNormalization2DY * topBottomSide;
            if (texcoordNormalization === 0.0 && topBottomSide < 0.0) {
              texcoordNormalization = 9.0; // some value greater than 1.0
            }
            texcoordNormalization2D[vec2Index + 1] = texcoordNormalization;
          }
        }
  
        // Adjust height of volume in 3D
        var adjustHeightStartBottom = adjustHeightStartBottomScratch;
        var adjustHeightEndBottom = adjustHeightEndBottomScratch;
        var adjustHeightStartTop = adjustHeightStartTopScratch;
        var adjustHeightEndTop = adjustHeightEndTopScratch;
  
        var getHeightsRectangle = Cartesian2.Rectangle.fromCartographicArray(
          getHeightCartographics,
          getHeightRectangleScratch
        );
        var minMaxHeights = ApproximateTerrainHeights.getMinimumMaximumHeights(
          getHeightsRectangle,
          ellipsoid
        );
        var minHeight = minMaxHeights.minimumTerrainHeight;
        var maxHeight = minMaxHeights.maximumTerrainHeight;
  
        sumHeights += minHeight;
        sumHeights += maxHeight;
  
        adjustHeights(
          startBottom,
          startTop,
          minHeight,
          maxHeight,
          adjustHeightStartBottom,
          adjustHeightStartTop
        );
        adjustHeights(
          endBottom,
          endTop,
          minHeight,
          maxHeight,
          adjustHeightEndBottom,
          adjustHeightEndTop
        );
  
        // Nudge the positions away from the "polyline" a little bit to prevent errors in GeometryPipeline
        var normalNudge = Cartesian2.Cartesian3.multiplyByScalar(
          rightNormal,
          _Math.CesiumMath.EPSILON5,
          normalNudgeScratch
        );
        Cartesian2.Cartesian3.add(
          adjustHeightStartBottom,
          normalNudge,
          adjustHeightStartBottom
        );
        Cartesian2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
        Cartesian2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
        Cartesian2.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  
        // If the segment is very close to the XZ plane, nudge the vertices slightly to avoid touching it.
        nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
        nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  
        Cartesian2.Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);
        Cartesian2.Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);
        Cartesian2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);
        Cartesian2.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);
  
        normalNudge = Cartesian2.Cartesian3.multiplyByScalar(
          rightNormal,
          -2.0 * _Math.CesiumMath.EPSILON5,
          normalNudgeScratch
        );
        Cartesian2.Cartesian3.add(
          adjustHeightStartBottom,
          normalNudge,
          adjustHeightStartBottom
        );
        Cartesian2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);
        Cartesian2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);
        Cartesian2.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);
  
        nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);
        nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);
  
        Cartesian2.Cartesian3.pack(
          adjustHeightStartBottom,
          positionsArray,
          vec3sWriteIndex + 12
        );
        Cartesian2.Cartesian3.pack(
          adjustHeightEndBottom,
          positionsArray,
          vec3sWriteIndex + 15
        );
        Cartesian2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);
        Cartesian2.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21);
  
        cartographicsIndex += 2;
        index += 3;
  
        vec2sWriteIndex += 16;
        vec3sWriteIndex += 24;
        vec4sWriteIndex += 32;
  
        lengthSoFar3D += segmentLength3D;
        lengthSoFar2D += segmentLength2D;
      }
  
      index = 0;
      var indexOffset = 0;
      for (i = 0; i < segmentCount; i++) {
        for (j = 0; j < REFERENCE_INDICES_LENGTH; j++) {
          indices[index + j] = REFERENCE_INDICES[j] + indexOffset;
        }
        indexOffset += 8;
        index += REFERENCE_INDICES_LENGTH;
      }
  
      var boundingSpheres = scratchBoundingSpheres;
      Transforms.BoundingSphere.fromVertices(
        bottomPositionsArray,
        Cartesian2.Cartesian3.ZERO,
        3,
        boundingSpheres[0]
      );
      Transforms.BoundingSphere.fromVertices(
        topPositionsArray,
        Cartesian2.Cartesian3.ZERO,
        3,
        boundingSpheres[1]
      );
      var boundingSphere = Transforms.BoundingSphere.fromBoundingSpheres(boundingSpheres);
  
      // Adjust bounding sphere height and radius to cover more of the volume
      boundingSphere.radius += sumHeights / (segmentCount * 2.0);
  
      var attributes = {
        position: new GeometryAttribute.GeometryAttribute({
          componentDatatype: ComponentDatatype.ComponentDatatype.DOUBLE,
          componentsPerAttribute: 3,
          normalize: false,
          values: positionsArray,
        }),
        startHiAndForwardOffsetX: getVec4GeometryAttribute(
          startHiAndForwardOffsetX
        ),
        startLoAndForwardOffsetY: getVec4GeometryAttribute(
          startLoAndForwardOffsetY
        ),
        startNormalAndForwardOffsetZ: getVec4GeometryAttribute(
          startNormalAndForwardOffsetZ
        ),
        endNormalAndTextureCoordinateNormalizationX: getVec4GeometryAttribute(
          endNormalAndTextureCoordinateNormalizationX
        ),
        rightNormalAndTextureCoordinateNormalizationY: getVec4GeometryAttribute(
          rightNormalAndTextureCoordinateNormalizationY
        ),
      };
  
      if (compute2dAttributes) {
        attributes.startHiLo2D = getVec4GeometryAttribute(startHiLo2D);
        attributes.offsetAndRight2D = getVec4GeometryAttribute(offsetAndRight2D);
        attributes.startEndNormals2D = getVec4GeometryAttribute(startEndNormals2D);
        attributes.texcoordNormalization2D = new GeometryAttribute.GeometryAttribute({
          componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
          componentsPerAttribute: 2,
          normalize: false,
          values: texcoordNormalization2D,
        });
      }
  
      return new GeometryAttribute.Geometry({
        attributes: attributes,
        indices: indices,
        boundingSphere: boundingSphere,
      });
    }
  
    function getVec4GeometryAttribute(typedArray) {
      return new GeometryAttribute.GeometryAttribute({
        componentDatatype: ComponentDatatype.ComponentDatatype.FLOAT,
        componentsPerAttribute: 4,
        normalize: false,
        values: typedArray,
      });
    }
  
    /**
     * Approximates an ellipsoid-tangent vector in 2D by projecting the end point into 2D.
     * Exposed for testing.
     *
     * @param {MapProjection} projection Map Projection for projecting coordinates to 2D.
     * @param {Cartographic} cartographic The cartographic origin point of the normal.
     *   Used to check if the normal crosses the IDL during projection.
     * @param {Cartesian3} normal The normal in 3D.
     * @param {Cartesian3} projectedPosition The projected origin point of the normal in 2D.
     * @param {Cartesian3} result Result parameter on which to store the projected normal.
     * @private
     */
    GroundPolylineGeometry._projectNormal = projectNormal;
  
    function createGroundPolylineGeometry(groundPolylineGeometry, offset) {
      return ApproximateTerrainHeights.initialize().then(function () {
        if (when.defined(offset)) {
          groundPolylineGeometry = GroundPolylineGeometry.unpack(
            groundPolylineGeometry,
            offset
          );
        }
        return GroundPolylineGeometry.createGeometry(groundPolylineGeometry);
      });
    }
  
    return createGroundPolylineGeometry;
  
  });
  