| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135 | /* This file is automatically rebuilt by the Cesium build process. */define(['./Transforms-323408fe', './Matrix2-69c32d33', './RuntimeError-c581ca93', './defaultValue-94c3e563', './ComponentDatatype-b1ea011a', './ArcType-0cf52f8c', './arrayRemoveDuplicates-87160c89', './EllipsoidGeodesic-98096082', './EllipsoidRhumbLine-5cb6da82', './EncodedCartesian3-b1f97f8a', './GeometryAttribute-cb73bb3f', './IntersectionTests-d5d945ac', './Plane-069b6800', './WebMercatorProjection-f88d3d05', './_commonjsHelpers-3aae1032-f55dc0c4', './combine-761d9c3f', './WebGLConstants-7dccdc96'], (function (Transforms, Matrix2, RuntimeError, defaultValue, ComponentDatatype, ArcType, arrayRemoveDuplicates, EllipsoidGeodesic, EllipsoidRhumbLine, EncodedCartesian3, GeometryAttribute, IntersectionTests, Plane, WebMercatorProjection, _commonjsHelpers3aae1032, combine, 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 = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);    this._ellipsoid = defaultValue.defaultValue(options.ellipsoid, Matrix2.Ellipsoid.WGS84);    this._rectangle = defaultValue.defaultValue(options.rectangle, Matrix2.Rectangle.MAX_VALUE);    this._projection = new Transforms.GeographicProjection(this._ellipsoid);    this._numberOfLevelZeroTilesX = defaultValue.defaultValue(      options.numberOfLevelZeroTilesX,      2    );    this._numberOfLevelZeroTilesY = defaultValue.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);    RuntimeError.Check.defined("rectangle", rectangle);    //>>includeEnd('debug');    const west = ComponentDatatype.CesiumMath.toDegrees(rectangle.west);    const south = ComponentDatatype.CesiumMath.toDegrees(rectangle.south);    const east = ComponentDatatype.CesiumMath.toDegrees(rectangle.east);    const north = ComponentDatatype.CesiumMath.toDegrees(rectangle.north);    if (!defaultValue.defined(result)) {      return new Matrix2.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  ) {    const rectangleRadians = this.tileXYToRectangle(x, y, level, result);    rectangleRadians.west = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.west);    rectangleRadians.south = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.south);    rectangleRadians.east = ComponentDatatype.CesiumMath.toDegrees(rectangleRadians.east);    rectangleRadians.north = ComponentDatatype.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  ) {    const rectangle = this._rectangle;    const xTiles = this.getNumberOfXTilesAtLevel(level);    const yTiles = this.getNumberOfYTilesAtLevel(level);    const xTileWidth = rectangle.width / xTiles;    const west = x * xTileWidth + rectangle.west;    const east = (x + 1) * xTileWidth + rectangle.west;    const yTileHeight = rectangle.height / yTiles;    const north = rectangle.north - y * yTileHeight;    const south = rectangle.north - (y + 1) * yTileHeight;    if (!defaultValue.defined(result)) {      result = new Matrix2.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  ) {    const rectangle = this._rectangle;    if (!Matrix2.Rectangle.contains(rectangle, position)) {      // outside the bounds of the tiling scheme      return undefined;    }    const xTiles = this.getNumberOfXTilesAtLevel(level);    const yTiles = this.getNumberOfYTilesAtLevel(level);    const xTileWidth = rectangle.width / xTiles;    const yTileHeight = rectangle.height / yTiles;    let longitude = position.longitude;    if (rectangle.east < rectangle.west) {      longitude += ComponentDatatype.CesiumMath.TWO_PI;    }    let xTileCoordinate = ((longitude - rectangle.west) / xTileWidth) | 0;    if (xTileCoordinate >= xTiles) {      xTileCoordinate = xTiles - 1;    }    let yTileCoordinate =      ((rectangle.north - position.latitude) / yTileHeight) | 0;    if (yTileCoordinate >= yTiles) {      yTileCoordinate = yTiles - 1;    }    if (!defaultValue.defined(result)) {      return new Matrix2.Cartesian2(xTileCoordinate, yTileCoordinate);    }    result.x = xTileCoordinate;    result.y = yTileCoordinate;    return result;  };  const scratchDiagonalCartesianNE = new Matrix2.Cartesian3();  const scratchDiagonalCartesianSW = new Matrix2.Cartesian3();  const scratchDiagonalCartographic = new Matrix2.Cartographic();  const scratchCenterCartesian = new Matrix2.Cartesian3();  const scratchSurfaceCartesian = new Matrix2.Cartesian3();  const scratchBoundingSphere = new Transforms.BoundingSphere();  const tilingScheme = new GeographicTilingScheme();  const scratchCorners = [    new Matrix2.Cartographic(),    new Matrix2.Cartographic(),    new Matrix2.Cartographic(),    new Matrix2.Cartographic(),  ];  const scratchTileXY = new Matrix2.Cartesian2();  /**   * A collection of functions for approximating terrain height   * @private   */  const ApproximateTerrainHeights = {};  /**   * Initializes the minimum and maximum terrain heights   * @return {Promise.<void>}   */  ApproximateTerrainHeights.initialize = function () {    let initPromise = ApproximateTerrainHeights._initPromise;    if (defaultValue.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);    RuntimeError.Check.defined("rectangle", rectangle);    if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {      throw new RuntimeError.DeveloperError(        "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"      );    }    //>>includeEnd('debug');    ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix2.Ellipsoid.WGS84);    const xyLevel = getTileXYLevel(rectangle);    // Get the terrain min/max for that tile    let minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;    let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;    if (defaultValue.defined(xyLevel)) {      const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;      const heights = ApproximateTerrainHeights._terrainHeights[key];      if (defaultValue.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(        Matrix2.Rectangle.northeast(rectangle, scratchDiagonalCartographic),        scratchDiagonalCartesianNE      );      ellipsoid.cartographicToCartesian(        Matrix2.Rectangle.southwest(rectangle, scratchDiagonalCartographic),        scratchDiagonalCartesianSW      );      Matrix2.Cartesian3.midpoint(        scratchDiagonalCartesianSW,        scratchDiagonalCartesianNE,        scratchCenterCartesian      );      const surfacePosition = ellipsoid.scaleToGeodeticSurface(        scratchCenterCartesian,        scratchSurfaceCartesian      );      if (defaultValue.defined(surfacePosition)) {        const distance = Matrix2.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);    RuntimeError.Check.defined("rectangle", rectangle);    if (!defaultValue.defined(ApproximateTerrainHeights._terrainHeights)) {      throw new RuntimeError.DeveloperError(        "You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"      );    }    //>>includeEnd('debug');    ellipsoid = defaultValue.defaultValue(ellipsoid, Matrix2.Ellipsoid.WGS84);    const xyLevel = getTileXYLevel(rectangle);    // Get the terrain max for that tile    let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;    if (defaultValue.defined(xyLevel)) {      const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;      const heights = ApproximateTerrainHeights._terrainHeights[key];      if (defaultValue.defined(heights)) {        maxTerrainHeight = heights[1];      }    }    const 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) {    Matrix2.Cartographic.fromRadians(      rectangle.east,      rectangle.north,      0.0,      scratchCorners[0]    );    Matrix2.Cartographic.fromRadians(      rectangle.west,      rectangle.north,      0.0,      scratchCorners[1]    );    Matrix2.Cartographic.fromRadians(      rectangle.east,      rectangle.south,      0.0,      scratchCorners[2]    );    Matrix2.Cartographic.fromRadians(      rectangle.west,      rectangle.south,      0.0,      scratchCorners[3]    );    // Determine which tile the bounding rectangle is in    let lastLevelX = 0,      lastLevelY = 0;    let currentX = 0,      currentY = 0;    const maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;    let i;    for (i = 0; i <= maxLevel; ++i) {      let failed = false;      for (let j = 0; j < 4; ++j) {        const 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 defaultValue.defined(ApproximateTerrainHeights._terrainHeights);      },    },  });  const PROJECTIONS = [Transforms.GeographicProjection, WebMercatorProjection.WebMercatorProjection];  const PROJECTION_COUNT = PROJECTIONS.length;  const MITER_BREAK_SMALL = Math.cos(ComponentDatatype.CesiumMath.toRadians(30.0));  const MITER_BREAK_LARGE = Math.cos(ComponentDatatype.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.  const WALL_INITIAL_MIN_HEIGHT = 0.0;  const 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   * const positions = Cesium.Cartesian3.fromDegreesArray([   *   -112.1340164450331, 36.05494287836128,   *   -112.08821010582645, 36.097804071380715,   *   -112.13296079730024, 36.168769146801104   * ]);   *   * const geometry = new Cesium.GroundPolylineGeometry({   *   positions : positions   * });   */  function GroundPolylineGeometry(options) {    options = defaultValue.defaultValue(options, defaultValue.defaultValue.EMPTY_OBJECT);    const positions = options.positions;    //>>includeStart('debug', pragmas.debug);    if (!defaultValue.defined(positions) || positions.length < 2) {      throw new RuntimeError.DeveloperError("At least two positions are required.");    }    if (      defaultValue.defined(options.arcType) &&      options.arcType !== ArcType.ArcType.GEODESIC &&      options.arcType !== ArcType.ArcType.RHUMB    ) {      throw new RuntimeError.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 = Matrix2.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 +          Matrix2.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  ) {    let projectionIndex = 0;    for (let i = 0; i < PROJECTION_COUNT; i++) {      if (mapProjection instanceof PROJECTIONS[i]) {        projectionIndex = i;        break;      }    }    groundPolylineGeometry._projectionIndex = projectionIndex;    groundPolylineGeometry._ellipsoid = mapProjection.ellipsoid;  };  const cart3Scratch1 = new Matrix2.Cartesian3();  const cart3Scratch2 = new Matrix2.Cartesian3();  const cart3Scratch3 = new Matrix2.Cartesian3();  function computeRightNormal(start, end, maxHeight, ellipsoid, result) {    const startBottom = getPosition(ellipsoid, start, 0.0, cart3Scratch1);    const startTop = getPosition(ellipsoid, start, maxHeight, cart3Scratch2);    const endBottom = getPosition(ellipsoid, end, 0.0, cart3Scratch3);    const up = direction(startTop, startBottom, cart3Scratch2);    const forward = direction(endBottom, startBottom, cart3Scratch3);    Matrix2.Cartesian3.cross(forward, up, result);    return Matrix2.Cartesian3.normalize(result, result);  }  const interpolatedCartographicScratch = new Matrix2.Cartographic();  const interpolatedBottomScratch = new Matrix2.Cartesian3();  const interpolatedTopScratch = new Matrix2.Cartesian3();  const interpolatedNormalScratch = new Matrix2.Cartesian3();  function interpolateSegment(    start,    end,    minHeight,    maxHeight,    granularity,    arcType,    ellipsoid,    normalsArray,    bottomPositionsArray,    topPositionsArray,    cartographicsArray  ) {    if (granularity === 0.0) {      return;    }    let 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);    }    const surfaceDistance = ellipsoidLine.surfaceDistance;    if (surfaceDistance < granularity) {      return;    }    // Compute rightwards normal applicable at all interpolated points    const interpolatedNormal = computeRightNormal(      start,      end,      maxHeight,      ellipsoid,      interpolatedNormalScratch    );    const segments = Math.ceil(surfaceDistance / granularity);    const interpointDistance = surfaceDistance / segments;    let distanceFromStart = interpointDistance;    const pointsToAdd = segments - 1;    let packIndex = normalsArray.length;    for (let i = 0; i < pointsToAdd; i++) {      const interpolatedCartographic = ellipsoidLine.interpolateUsingSurfaceDistance(        distanceFromStart,        interpolatedCartographicScratch      );      const interpolatedBottom = getPosition(        ellipsoid,        interpolatedCartographic,        minHeight,        interpolatedBottomScratch      );      const interpolatedTop = getPosition(        ellipsoid,        interpolatedCartographic,        maxHeight,        interpolatedTopScratch      );      Matrix2.Cartesian3.pack(interpolatedNormal, normalsArray, packIndex);      Matrix2.Cartesian3.pack(interpolatedBottom, bottomPositionsArray, packIndex);      Matrix2.Cartesian3.pack(interpolatedTop, topPositionsArray, packIndex);      cartographicsArray.push(interpolatedCartographic.latitude);      cartographicsArray.push(interpolatedCartographic.longitude);      packIndex += 3;      distanceFromStart += interpointDistance;    }  }  const heightlessCartographicScratch = new Matrix2.Cartographic();  function getPosition(ellipsoid, cartographic, height, result) {    Matrix2.Cartographic.clone(cartographic, heightlessCartographicScratch);    heightlessCartographicScratch.height = height;    return Matrix2.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);    RuntimeError.Check.typeOf.object("value", value);    RuntimeError.Check.defined("array", array);    //>>includeEnd('debug');    let index = defaultValue.defaultValue(startingIndex, 0);    const positions = value._positions;    const positionsLength = positions.length;    array[index++] = positionsLength;    for (let i = 0; i < positionsLength; ++i) {      const cartesian = positions[i];      Matrix2.Cartesian3.pack(cartesian, array, index);      index += 3;    }    array[index++] = value.granularity;    array[index++] = value.loop ? 1.0 : 0.0;    array[index++] = value.arcType;    Matrix2.Ellipsoid.pack(value._ellipsoid, array, index);    index += Matrix2.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);    RuntimeError.Check.defined("array", array);    //>>includeEnd('debug');    let index = defaultValue.defaultValue(startingIndex, 0);    const positionsLength = array[index++];    const positions = new Array(positionsLength);    for (let i = 0; i < positionsLength; i++) {      positions[i] = Matrix2.Cartesian3.unpack(array, index);      index += 3;    }    const granularity = array[index++];    const loop = array[index++] === 1.0;    const arcType = array[index++];    const ellipsoid = Matrix2.Ellipsoid.unpack(array, index);    index += Matrix2.Ellipsoid.packedLength;    const projectionIndex = array[index++];    const scene3DOnly = array[index++] === 1.0;    if (!defaultValue.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) {    Matrix2.Cartesian3.subtract(target, origin, result);    Matrix2.Cartesian3.normalize(result, result);    return result;  }  function tangentDirection(target, origin, up, result) {    result = direction(target, origin, result);    // orthogonalize    result = Matrix2.Cartesian3.cross(result, up, result);    result = Matrix2.Cartesian3.normalize(result, result);    result = Matrix2.Cartesian3.cross(up, result, result);    return result;  }  const toPreviousScratch = new Matrix2.Cartesian3();  const toNextScratch = new Matrix2.Cartesian3();  const forwardScratch = new Matrix2.Cartesian3();  const vertexUpScratch = new Matrix2.Cartesian3();  const cosine90 = 0.0;  const cosine180 = -1.0;  function computeVertexMiterNormal(    previousBottom,    vertexBottom,    vertexTop,    nextBottom,    result  ) {    const up = direction(vertexTop, vertexBottom, vertexUpScratch);    // Compute vectors pointing towards neighboring points but tangent to this point on the ellipsoid    const toPrevious = tangentDirection(      previousBottom,      vertexBottom,      up,      toPreviousScratch    );    const toNext = tangentDirection(nextBottom, vertexBottom, up, toNextScratch);    // Check if tangents are almost opposite - if so, no need to miter.    if (      ComponentDatatype.CesiumMath.equalsEpsilon(        Matrix2.Cartesian3.dot(toPrevious, toNext),        cosine180,        ComponentDatatype.CesiumMath.EPSILON5      )    ) {      result = Matrix2.Cartesian3.cross(up, toPrevious, result);      result = Matrix2.Cartesian3.normalize(result, result);      return result;    }    // Average directions to previous and to next in the plane of Up    result = Matrix2.Cartesian3.add(toNext, toPrevious, result);    result = Matrix2.Cartesian3.normalize(result, result);    // Flip the normal if it isn't pointing roughly bound right (aka if forward is pointing more "backwards")    const forward = Matrix2.Cartesian3.cross(up, result, forwardScratch);    if (Matrix2.Cartesian3.dot(toNext, forward) < cosine90) {      result = Matrix2.Cartesian3.negate(result, result);    }    return result;  }  const XZ_PLANE = Plane.Plane.fromPointNormal(Matrix2.Cartesian3.ZERO, Matrix2.Cartesian3.UNIT_Y);  const previousBottomScratch = new Matrix2.Cartesian3();  const vertexBottomScratch = new Matrix2.Cartesian3();  const vertexTopScratch = new Matrix2.Cartesian3();  const nextBottomScratch = new Matrix2.Cartesian3();  const vertexNormalScratch = new Matrix2.Cartesian3();  const intersectionScratch = new Matrix2.Cartesian3();  const cartographicScratch0 = new Matrix2.Cartographic();  const cartographicScratch1 = new Matrix2.Cartographic();  const cartographicIntersectionScratch = new Matrix2.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) {    const compute2dAttributes = !groundPolylineGeometry._scene3DOnly;    let loop = groundPolylineGeometry.loop;    const ellipsoid = groundPolylineGeometry._ellipsoid;    const granularity = groundPolylineGeometry.granularity;    const arcType = groundPolylineGeometry.arcType;    const projection = new PROJECTIONS[groundPolylineGeometry._projectionIndex](      ellipsoid    );    const minHeight = WALL_INITIAL_MIN_HEIGHT;    const maxHeight = WALL_INITIAL_MAX_HEIGHT;    let index;    let i;    const positions = groundPolylineGeometry._positions;    const 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.    let p0;    let p1;    let c0;    let c1;    const rhumbLine = new EllipsoidRhumbLine.EllipsoidRhumbLine(undefined, undefined, ellipsoid);    let intersection;    let intersectionCartographic;    let intersectionLongitude;    const 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 (        defaultValue.defined(intersection) &&        !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&        !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)      ) {        if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {          splitPositions.push(Matrix2.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 (            defaultValue.defined(intersection) &&            !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&            !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)          ) {            splitPositions.push(Matrix2.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 (        defaultValue.defined(intersection) &&        !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&        !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)      ) {        if (groundPolylineGeometry.arcType === ArcType.ArcType.GEODESIC) {          splitPositions.push(Matrix2.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 (            defaultValue.defined(intersection) &&            !Matrix2.Cartesian3.equalsEpsilon(intersection, p0, ComponentDatatype.CesiumMath.EPSILON7) &&            !Matrix2.Cartesian3.equalsEpsilon(intersection, p1, ComponentDatatype.CesiumMath.EPSILON7)          ) {            splitPositions.push(Matrix2.Cartesian3.clone(intersection));          }        }      }    }    let cartographicsLength = splitPositions.length;    let cartographics = new Array(cartographicsLength);    for (i = 0; i < cartographicsLength; i++) {      const cartographic = Matrix2.Cartographic.fromCartesian(        splitPositions[i],        ellipsoid      );      cartographic.height = 0.0;      cartographics[i] = cartographic;    }    cartographics = arrayRemoveDuplicates.arrayRemoveDuplicates(      cartographics,      Matrix2.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.    const cartographicsArray = [];    const normalsArray = [];    const bottomPositionsArray = [];    const topPositionsArray = [];    let previousBottom = previousBottomScratch;    let vertexBottom = vertexBottomScratch;    let vertexTop = vertexTopScratch;    let nextBottom = nextBottomScratch;    let vertexNormal = vertexNormalScratch;    // First point - either loop or attach a "perpendicular" normal    const startCartographic = cartographics[0];    const nextCartographic = cartographics[1];    const 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      );    }    Matrix2.Cartesian3.pack(vertexNormal, normalsArray, 0);    Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, 0);    Matrix2.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 = Matrix2.Cartesian3.clone(vertexBottom, previousBottom);      vertexBottom = Matrix2.Cartesian3.clone(nextBottom, vertexBottom);      const vertexCartographic = cartographics[i];      getPosition(ellipsoid, vertexCartographic, maxHeight, vertexTop);      getPosition(ellipsoid, cartographics[i + 1], minHeight, nextBottom);      computeVertexMiterNormal(        previousBottom,        vertexBottom,        vertexTop,        nextBottom,        vertexNormal      );      index = normalsArray.length;      Matrix2.Cartesian3.pack(vertexNormal, normalsArray, index);      Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);      Matrix2.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.    const endCartographic = cartographics[cartographicsLength - 1];    const preEndCartographic = cartographics[cartographicsLength - 2];    vertexBottom = getPosition(      ellipsoid,      endCartographic,      minHeight,      vertexBottom    );    vertexTop = getPosition(ellipsoid, endCartographic, maxHeight, vertexTop);    if (loop) {      const 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;    Matrix2.Cartesian3.pack(vertexNormal, normalsArray, index);    Matrix2.Cartesian3.pack(vertexBottom, bottomPositionsArray, index);    Matrix2.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.  const lineDirectionScratch = new Matrix2.Cartesian3();  const matrix3Scratch = new Matrix2.Matrix3();  const quaternionScratch = new Transforms.Quaternion();  function breakMiter(endGeometryNormal, startBottom, endBottom, endTop) {    const lineDirection = direction(endBottom, startBottom, lineDirectionScratch);    const dot = Matrix2.Cartesian3.dot(lineDirection, endGeometryNormal);    if (dot > MITER_BREAK_SMALL || dot < MITER_BREAK_LARGE) {      const vertexUp = direction(endTop, endBottom, vertexUpScratch);      const angle =        dot < MITER_BREAK_LARGE          ? ComponentDatatype.CesiumMath.PI_OVER_TWO          : -ComponentDatatype.CesiumMath.PI_OVER_TWO;      const quaternion = Transforms.Quaternion.fromAxisAngle(        vertexUp,        angle,        quaternionScratch      );      const rotationMatrix = Matrix2.Matrix3.fromQuaternion(quaternion, matrix3Scratch);      Matrix2.Matrix3.multiplyByVector(        rotationMatrix,        endGeometryNormal,        endGeometryNormal      );      return true;    }    return false;  }  const endPosCartographicScratch = new Matrix2.Cartographic();  const normalStartpointScratch = new Matrix2.Cartesian3();  const normalEndpointScratch = new Matrix2.Cartesian3();  function projectNormal(    projection,    cartographic,    normal,    projectedPosition,    result  ) {    const position = Matrix2.Cartographic.toCartesian(      cartographic,      projection._ellipsoid,      normalStartpointScratch    );    let normalEndpoint = Matrix2.Cartesian3.add(position, normal, normalEndpointScratch);    let flipNormal = false;    const ellipsoid = projection._ellipsoid;    let 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) >      ComponentDatatype.CesiumMath.PI_OVER_TWO    ) {      flipNormal = true;      normalEndpoint = Matrix2.Cartesian3.subtract(        position,        normal,        normalEndpointScratch      );      normalEndpointCartographic = ellipsoid.cartesianToCartographic(        normalEndpoint,        endPosCartographicScratch      );    }    normalEndpointCartographic.height = 0.0;    const normalEndpointProjected = projection.project(      normalEndpointCartographic,      result    );    result = Matrix2.Cartesian3.subtract(      normalEndpointProjected,      projectedPosition,      result    );    result.z = 0.0;    result = Matrix2.Cartesian3.normalize(result, result);    if (flipNormal) {      Matrix2.Cartesian3.negate(result, result);    }    return result;  }  const adjustHeightNormalScratch = new Matrix2.Cartesian3();  const adjustHeightOffsetScratch = new Matrix2.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    const adjustHeightNormal = Matrix2.Cartesian3.subtract(      top,      bottom,      adjustHeightNormalScratch    );    Matrix2.Cartesian3.normalize(adjustHeightNormal, adjustHeightNormal);    const distanceForBottom = minHeight - WALL_INITIAL_MIN_HEIGHT;    let adjustHeightOffset = Matrix2.Cartesian3.multiplyByScalar(      adjustHeightNormal,      distanceForBottom,      adjustHeightOffsetScratch    );    Matrix2.Cartesian3.add(bottom, adjustHeightOffset, adjustHeightBottom);    const distanceForTop = maxHeight - WALL_INITIAL_MAX_HEIGHT;    adjustHeightOffset = Matrix2.Cartesian3.multiplyByScalar(      adjustHeightNormal,      distanceForTop,      adjustHeightOffsetScratch    );    Matrix2.Cartesian3.add(top, adjustHeightOffset, adjustHeightTop);  }  const nudgeDirectionScratch = new Matrix2.Cartesian3();  function nudgeXZ(start, end) {    const startToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, start);    const endToXZdistance = Plane.Plane.getPointDistance(XZ_PLANE, end);    let offset = nudgeDirectionScratch;    // Larger epsilon than what's used in GeometryPipeline, a centimeter in world space    if (ComponentDatatype.CesiumMath.equalsEpsilon(startToXZdistance, 0.0, ComponentDatatype.CesiumMath.EPSILON2)) {      offset = direction(end, start, offset);      Matrix2.Cartesian3.multiplyByScalar(offset, ComponentDatatype.CesiumMath.EPSILON2, offset);      Matrix2.Cartesian3.add(start, offset, start);    } else if (      ComponentDatatype.CesiumMath.equalsEpsilon(endToXZdistance, 0.0, ComponentDatatype.CesiumMath.EPSILON2)    ) {      offset = direction(start, end, offset);      Matrix2.Cartesian3.multiplyByScalar(offset, ComponentDatatype.CesiumMath.EPSILON2, offset);      Matrix2.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) {    const absStartLon = Math.abs(start.longitude);    const absEndLon = Math.abs(end.longitude);    if (      ComponentDatatype.CesiumMath.equalsEpsilon(absStartLon, ComponentDatatype.CesiumMath.PI, ComponentDatatype.CesiumMath.EPSILON11)    ) {      const endSign = ComponentDatatype.CesiumMath.sign(end.longitude);      start.longitude = endSign * (absStartLon - ComponentDatatype.CesiumMath.EPSILON11);      return 1;    } else if (      ComponentDatatype.CesiumMath.equalsEpsilon(absEndLon, ComponentDatatype.CesiumMath.PI, ComponentDatatype.CesiumMath.EPSILON11)    ) {      const startSign = ComponentDatatype.CesiumMath.sign(start.longitude);      end.longitude = startSign * (absEndLon - ComponentDatatype.CesiumMath.EPSILON11);      return 2;    }    return 0;  }  const startCartographicScratch = new Matrix2.Cartographic();  const endCartographicScratch = new Matrix2.Cartographic();  const segmentStartTopScratch = new Matrix2.Cartesian3();  const segmentEndTopScratch = new Matrix2.Cartesian3();  const segmentStartBottomScratch = new Matrix2.Cartesian3();  const segmentEndBottomScratch = new Matrix2.Cartesian3();  const segmentStartNormalScratch = new Matrix2.Cartesian3();  const segmentEndNormalScratch = new Matrix2.Cartesian3();  const getHeightCartographics = [    startCartographicScratch,    endCartographicScratch,  ];  const getHeightRectangleScratch = new Matrix2.Rectangle();  const adjustHeightStartTopScratch = new Matrix2.Cartesian3();  const adjustHeightEndTopScratch = new Matrix2.Cartesian3();  const adjustHeightStartBottomScratch = new Matrix2.Cartesian3();  const adjustHeightEndBottomScratch = new Matrix2.Cartesian3();  const segmentStart2DScratch = new Matrix2.Cartesian3();  const segmentEnd2DScratch = new Matrix2.Cartesian3();  const segmentStartNormal2DScratch = new Matrix2.Cartesian3();  const segmentEndNormal2DScratch = new Matrix2.Cartesian3();  const offsetScratch = new Matrix2.Cartesian3();  const startUpScratch = new Matrix2.Cartesian3();  const endUpScratch = new Matrix2.Cartesian3();  const rightScratch = new Matrix2.Cartesian3();  const startPlaneNormalScratch = new Matrix2.Cartesian3();  const endPlaneNormalScratch = new Matrix2.Cartesian3();  const encodeScratch = new EncodedCartesian3.EncodedCartesian3();  const encodeScratch2D = new EncodedCartesian3.EncodedCartesian3();  const forwardOffset2DScratch = new Matrix2.Cartesian3();  const right2DScratch = new Matrix2.Cartesian3();  const normalNudgeScratch = new Matrix2.Cartesian3();  const scratchBoundingSpheres = [new Transforms.BoundingSphere(), new Transforms.BoundingSphere()];  // Winding order is reversed so each segment's volume is inside-out  const 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  ];  const 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  ) {    let i;    let index;    const ellipsoid = projection._ellipsoid;    // Each segment will have 8 vertices    const segmentCount = bottomPositionsArray.length / 3 - 1;    const vertexCount = segmentCount * 8;    const arraySizeVec4 = vertexCount * 4;    const indexCount = segmentCount * 36;    const indices =      vertexCount > 65535        ? new Uint32Array(indexCount)        : new Uint16Array(indexCount);    const positionsArray = new Float64Array(vertexCount * 3);    const startHiAndForwardOffsetX = new Float32Array(arraySizeVec4);    const startLoAndForwardOffsetY = new Float32Array(arraySizeVec4);    const startNormalAndForwardOffsetZ = new Float32Array(arraySizeVec4);    const endNormalAndTextureCoordinateNormalizationX = new Float32Array(      arraySizeVec4    );    const rightNormalAndTextureCoordinateNormalizationY = new Float32Array(      arraySizeVec4    );    let startHiLo2D;    let offsetAndRight2D;    let startEndNormals2D;    let 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    const cartographicsLength = cartographicsArray.length / 2;    let length2D = 0.0;    const startCartographic = startCartographicScratch;    startCartographic.height = 0.0;    const endCartographic = endCartographicScratch;    endCartographic.height = 0.0;    let segmentStartCartesian = segmentStartTopScratch;    let 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 += Matrix2.Cartesian3.distance(          segmentStartCartesian,          segmentEndCartesian        );        index += 2;      }    }    // 3D    const positionsLength = topPositionsArray.length / 3;    segmentEndCartesian = Matrix2.Cartesian3.unpack(      topPositionsArray,      0,      segmentEndCartesian    );    let length3D = 0.0;    index = 3;    for (i = 1; i < positionsLength; i++) {      segmentStartCartesian = Matrix2.Cartesian3.clone(        segmentEndCartesian,        segmentStartCartesian      );      segmentEndCartesian = Matrix2.Cartesian3.unpack(        topPositionsArray,        index,        segmentEndCartesian      );      length3D += Matrix2.Cartesian3.distance(segmentStartCartesian, segmentEndCartesian);      index += 3;    }    /*** Generate segments ***/    let j;    index = 3;    let cartographicsIndex = 0;    let vec2sWriteIndex = 0;    let vec3sWriteIndex = 0;    let vec4sWriteIndex = 0;    let miterBroken = false;    let endBottom = Matrix2.Cartesian3.unpack(      bottomPositionsArray,      0,      segmentEndBottomScratch    );    let endTop = Matrix2.Cartesian3.unpack(topPositionsArray, 0, segmentEndTopScratch);    let endGeometryNormal = Matrix2.Cartesian3.unpack(      normalsArray,      0,      segmentEndNormalScratch    );    if (loop) {      const preEndBottom = Matrix2.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 = Matrix2.Cartesian3.negate(          endGeometryNormal,          endGeometryNormal        );      }    }    let lengthSoFar3D = 0.0;    let lengthSoFar2D = 0.0;    // For translating bounding volume    let sumHeights = 0.0;    for (i = 0; i < segmentCount; i++) {      const startBottom = Matrix2.Cartesian3.clone(endBottom, segmentStartBottomScratch);      const startTop = Matrix2.Cartesian3.clone(endTop, segmentStartTopScratch);      let startGeometryNormal = Matrix2.Cartesian3.clone(        endGeometryNormal,        segmentStartNormalScratch      );      if (miterBroken) {        startGeometryNormal = Matrix2.Cartesian3.negate(          startGeometryNormal,          startGeometryNormal        );      }      endBottom = Matrix2.Cartesian3.unpack(        bottomPositionsArray,        index,        segmentEndBottomScratch      );      endTop = Matrix2.Cartesian3.unpack(topPositionsArray, index, segmentEndTopScratch);      endGeometryNormal = Matrix2.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];      let start2D;      let end2D;      let startGeometryNormal2D;      let endGeometryNormal2D;      if (compute2dAttributes) {        const nudgeResult = nudgeCartographic(startCartographic, endCartographic);        start2D = projection.project(startCartographic, segmentStart2DScratch);        end2D = projection.project(endCartographic, segmentEnd2DScratch);        const direction2D = direction(end2D, start2D, forwardOffset2DScratch);        direction2D.y = Math.abs(direction2D.y);        startGeometryNormal2D = segmentStartNormal2DScratch;        endGeometryNormal2D = segmentEndNormal2DScratch;        if (          nudgeResult === 0 ||          Matrix2.Cartesian3.dot(direction2D, Matrix2.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 = ComponentDatatype.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 = ComponentDatatype.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 */      const segmentLength3D = Matrix2.Cartesian3.distance(startTop, endTop);      const encodedStart = EncodedCartesian3.EncodedCartesian3.fromCartesian(        startBottom,        encodeScratch      );      const forwardOffset = Matrix2.Cartesian3.subtract(        endBottom,        startBottom,        offsetScratch      );      const forward = Matrix2.Cartesian3.normalize(forwardOffset, rightScratch);      let startUp = Matrix2.Cartesian3.subtract(startTop, startBottom, startUpScratch);      startUp = Matrix2.Cartesian3.normalize(startUp, startUp);      let rightNormal = Matrix2.Cartesian3.cross(forward, startUp, rightScratch);      rightNormal = Matrix2.Cartesian3.normalize(rightNormal, rightNormal);      let startPlaneNormal = Matrix2.Cartesian3.cross(        startUp,        startGeometryNormal,        startPlaneNormalScratch      );      startPlaneNormal = Matrix2.Cartesian3.normalize(startPlaneNormal, startPlaneNormal);      let endUp = Matrix2.Cartesian3.subtract(endTop, endBottom, endUpScratch);      endUp = Matrix2.Cartesian3.normalize(endUp, endUp);      let endPlaneNormal = Matrix2.Cartesian3.cross(        endGeometryNormal,        endUp,        endPlaneNormalScratch      );      endPlaneNormal = Matrix2.Cartesian3.normalize(endPlaneNormal, endPlaneNormal);      const texcoordNormalization3DX = segmentLength3D / length3D;      const texcoordNormalization3DY = lengthSoFar3D / length3D;      /* 2D */      let segmentLength2D = 0.0;      let encodedStart2D;      let forwardOffset2D;      let right2D;      let texcoordNormalization2DX = 0.0;      let texcoordNormalization2DY = 0.0;      if (compute2dAttributes) {        segmentLength2D = Matrix2.Cartesian3.distance(start2D, end2D);        encodedStart2D = EncodedCartesian3.EncodedCartesian3.fromCartesian(          start2D,          encodeScratch2D        );        forwardOffset2D = Matrix2.Cartesian3.subtract(          end2D,          start2D,          forwardOffset2DScratch        );        // Right direction is just forward direction rotated by -90 degrees around Z        // Similarly with plane normals        right2D = Matrix2.Cartesian3.normalize(forwardOffset2D, right2DScratch);        const swap = right2D.x;        right2D.x = right2D.y;        right2D.y = -swap;        texcoordNormalization2DX = segmentLength2D / length2D;        texcoordNormalization2DY = lengthSoFar2D / length2D;      }      /** Pack **/      for (j = 0; j < 8; j++) {        const vec4Index = vec4sWriteIndex + j * 4;        const vec2Index = vec2sWriteIndex + j * 2;        const 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.        const rightPlaneSide = j < 4 ? 1.0 : -1.0;        const topBottomSide =          j === 2 || j === 3 || j === 6 || j === 7 ? 1.0 : -1.0;        // 3D        Matrix2.Cartesian3.pack(encodedStart.high, startHiAndForwardOffsetX, vec4Index);        startHiAndForwardOffsetX[wIndex] = forwardOffset.x;        Matrix2.Cartesian3.pack(encodedStart.low, startLoAndForwardOffsetY, vec4Index);        startLoAndForwardOffsetY[wIndex] = forwardOffset.y;        Matrix2.Cartesian3.pack(          startPlaneNormal,          startNormalAndForwardOffsetZ,          vec4Index        );        startNormalAndForwardOffsetZ[wIndex] = forwardOffset.z;        Matrix2.Cartesian3.pack(          endPlaneNormal,          endNormalAndTextureCoordinateNormalizationX,          vec4Index        );        endNormalAndTextureCoordinateNormalizationX[wIndex] =          texcoordNormalization3DX * rightPlaneSide;        Matrix2.Cartesian3.pack(          rightNormal,          rightNormalAndTextureCoordinateNormalizationY,          vec4Index        );        let 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      const adjustHeightStartBottom = adjustHeightStartBottomScratch;      const adjustHeightEndBottom = adjustHeightEndBottomScratch;      const adjustHeightStartTop = adjustHeightStartTopScratch;      const adjustHeightEndTop = adjustHeightEndTopScratch;      const getHeightsRectangle = Matrix2.Rectangle.fromCartographicArray(        getHeightCartographics,        getHeightRectangleScratch      );      const minMaxHeights = ApproximateTerrainHeights.getMinimumMaximumHeights(        getHeightsRectangle,        ellipsoid      );      const minHeight = minMaxHeights.minimumTerrainHeight;      const 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      let normalNudge = Matrix2.Cartesian3.multiplyByScalar(        rightNormal,        ComponentDatatype.CesiumMath.EPSILON5,        normalNudgeScratch      );      Matrix2.Cartesian3.add(        adjustHeightStartBottom,        normalNudge,        adjustHeightStartBottom      );      Matrix2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);      Matrix2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);      Matrix2.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);      Matrix2.Cartesian3.pack(adjustHeightStartBottom, positionsArray, vec3sWriteIndex);      Matrix2.Cartesian3.pack(adjustHeightEndBottom, positionsArray, vec3sWriteIndex + 3);      Matrix2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 6);      Matrix2.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 9);      normalNudge = Matrix2.Cartesian3.multiplyByScalar(        rightNormal,        -2.0 * ComponentDatatype.CesiumMath.EPSILON5,        normalNudgeScratch      );      Matrix2.Cartesian3.add(        adjustHeightStartBottom,        normalNudge,        adjustHeightStartBottom      );      Matrix2.Cartesian3.add(adjustHeightEndBottom, normalNudge, adjustHeightEndBottom);      Matrix2.Cartesian3.add(adjustHeightStartTop, normalNudge, adjustHeightStartTop);      Matrix2.Cartesian3.add(adjustHeightEndTop, normalNudge, adjustHeightEndTop);      nudgeXZ(adjustHeightStartBottom, adjustHeightEndBottom);      nudgeXZ(adjustHeightStartTop, adjustHeightEndTop);      Matrix2.Cartesian3.pack(        adjustHeightStartBottom,        positionsArray,        vec3sWriteIndex + 12      );      Matrix2.Cartesian3.pack(        adjustHeightEndBottom,        positionsArray,        vec3sWriteIndex + 15      );      Matrix2.Cartesian3.pack(adjustHeightEndTop, positionsArray, vec3sWriteIndex + 18);      Matrix2.Cartesian3.pack(adjustHeightStartTop, positionsArray, vec3sWriteIndex + 21);      cartographicsIndex += 2;      index += 3;      vec2sWriteIndex += 16;      vec3sWriteIndex += 24;      vec4sWriteIndex += 32;      lengthSoFar3D += segmentLength3D;      lengthSoFar2D += segmentLength2D;    }    index = 0;    let 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;    }    const boundingSpheres = scratchBoundingSpheres;    Transforms.BoundingSphere.fromVertices(      bottomPositionsArray,      Matrix2.Cartesian3.ZERO,      3,      boundingSpheres[0]    );    Transforms.BoundingSphere.fromVertices(      topPositionsArray,      Matrix2.Cartesian3.ZERO,      3,      boundingSpheres[1]    );    const boundingSphere = Transforms.BoundingSphere.fromBoundingSpheres(boundingSpheres);    // Adjust bounding sphere height and radius to cover more of the volume    boundingSphere.radius += sumHeights / (segmentCount * 2.0);    const 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 (defaultValue.defined(offset)) {        groundPolylineGeometry = GroundPolylineGeometry.unpack(          groundPolylineGeometry,          offset        );      }      return GroundPolylineGeometry.createGeometry(groundPolylineGeometry);    });  }  return createGroundPolylineGeometry;}));
 |