text: /* This file is automatically rebuilt by the Cesium build process. */
  define(['./defined-26bd4a03', './Check-da037458', './freezeObject-2d83f591', './defaultValue-f2e68450', './Math-fa6e45cb', './Cartesian2-2a723276', './defineProperties-6f7a50f2', './Transforms-a312718d', './RuntimeError-ad75c885', './WebGLConstants-497deb20', './ComponentDatatype-69643096', './GeometryAttribute-bb8a556c', './when-ee12a2cb', './EncodedCartesian3-8b2b90d0', './IntersectionTests-a83a53f7', './Plane-c601d1ec', './WebMercatorProjection-f2dc467d', './arrayRemoveDuplicates-dd708d81', './ArcType-d521909b', './EllipsoidRhumbLine-c6cdbfd3', './EllipsoidGeodesic-53e988a6'], function (defined, Check, freezeObject, defaultValue, _Math, Cartesian2, defineProperties, Transforms, RuntimeError, WebGLConstants, ComponentDatatype, GeometryAttribute, when, EncodedCartesian3, IntersectionTests, Plane, WebMercatorProjection, arrayRemoveDuplicates, ArcType, EllipsoidRhumbLine, EllipsoidGeodesic) { '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 = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);
  
              this._ellipsoid = defaultValue.defaultValue(options.ellipsoid, Cartesian2.Ellipsoid.WGS84);
              this._rectangle = defaultValue.defaultValue(options.rectangle, Cartesian2.Rectangle.MAX_VALUE);
              this._projection = new Transforms.GeographicProjection(this._ellipsoid);
              this._numberOfLevelZeroTilesX = defaultValue.defaultValue(options.numberOfLevelZeroTilesX, 2);
              this._numberOfLevelZeroTilesY = defaultValue.defaultValue(options.numberOfLevelZeroTilesY, 1);
          }
  
          defineProperties.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 (!defined.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 (!defined.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 (!defined.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}
           */
          ApproximateTerrainHeights.initialize = function() {
              var initPromise = ApproximateTerrainHeights._initPromise;
              if (defined.defined(initPromise)) {
                  return initPromise;
              }
  
              initPromise = Transforms.Resource.fetchJson(Transforms.buildModuleUrl('Assets/approximateTerrainHeights.json'))
                  .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 (!defined.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 = defaultValue.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 (defined.defined(xyLevel)) {
                  var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
                  var heights = ApproximateTerrainHeights._terrainHeights[key];
                  if (defined.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 (defined.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 (!defined.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 = defaultValue.defaultValue(ellipsoid, Cartesian2.Ellipsoid.WGS84);
  
              var xyLevel = getTileXYLevel(rectangle);
  
              // Get the terrain max for that tile
              var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
              if (defined.defined(xyLevel)) {
                  var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
                  var heights = ApproximateTerrainHeights._terrainHeights[key];
                  if (defined.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;
  
          defineProperties.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 defined.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 = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);
              var positions = options.positions;
  
              //>>includeStart('debug', pragmas.debug);
              if ((!defined.defined(positions)) || (positions.length < 2)) {
                  throw new Check.DeveloperError('At least two positions are required.');
              }
              if (defined.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 = defaultValue.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 = defaultValue.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 = defaultValue.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 = defaultValue.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;
          }
  
          defineProperties.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 = defaultValue.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 = defaultValue.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 (!defined.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 (defined.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 (defined.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 (defined.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 (defined.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 (defined.defined(offset)) {
                          groundPolylineGeometry = GroundPolylineGeometry.unpack(groundPolylineGeometry, offset);
                      }
                      return GroundPolylineGeometry.createGeometry(groundPolylineGeometry);
                  });
          }
  
      return createGroundPolylineGeometry;
  
  });
  