text: define([
          '../Core/BoundingSphere',
          '../Core/buildModuleUrl',
          '../Core/Cartesian2',
          '../Core/Cartesian3',
          '../Core/Cartographic',
          '../Core/defaultValue',
          '../Core/defined',
          '../Core/defineProperties',
          '../Core/destroyObject',
          '../Core/DeveloperError',
          '../Core/GeographicTilingScheme',
          '../Core/GeometryInstance',
          '../Core/isArray',
          '../Core/Math',
          '../Core/OrientedBoundingBox',
          '../Core/Rectangle',
          '../Core/Resource',
          '../Renderer/Pass',
          '../ThirdParty/when',
          './ClassificationPrimitive',
          './ClassificationType',
          './SceneMode'
      ], function(
          BoundingSphere,
          buildModuleUrl,
          Cartesian2,
          Cartesian3,
          Cartographic,
          defaultValue,
          defined,
          defineProperties,
          destroyObject,
          DeveloperError,
          GeographicTilingScheme,
          GeometryInstance,
          isArray,
          CesiumMath,
          OrientedBoundingBox,
          Rectangle,
          Resource,
          Pass,
          when,
          ClassificationPrimitive,
          ClassificationType,
          SceneMode) {
      'use strict';
  
      var GroundPrimitiveUniformMap = {
          u_globeMinimumAltitude: function() {
              return 55000.0;
          }
      };
  
      /**
       * A ground primitive represents geometry draped over the terrain in the {@link Scene}.  The geometry must be from a single {@link GeometryInstance}.
       * Batching multiple geometries is not yet supported.
       * <p>
       * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including
       * {@link Material} and {@link RenderState}.  Roughly, the geometry instance defines the structure and placement,
       * and the appearance defines the visual characteristics.  Decoupling geometry and appearance allows us to mix
       * and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance}
       * is supported at this time.
       * </p>
       * <p>
       * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there
       * will be rendering artifacts for some viewing angles.
       * </p>
       * <p>
       * Valid geometries are {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}.
       * </p>
       *
       * @alias GroundPrimitive
       * @constructor
       *
       * @param {Object} [options] Object with the following properties:
       * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render.
       * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
       * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
       * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
       * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
       * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
       * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
       * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first.
       * @param {ClassificationType} [options.classificationType=ClassificationType.BOTH] Determines whether terrain, 3D Tiles or both will be classified.
       * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
       * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be <code>true</code> on
       *                  creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be <code>false</code>.
       *
       * @example
       * // Example 1: Create primitive with a single instance
       * var rectangleInstance = new Cesium.GeometryInstance({
       *   geometry : new Cesium.RectangleGeometry({
       *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
       *   }),
       *   id : 'rectangle',
       *   attributes : {
       *     color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
       *   }
       * });
       * scene.primitives.add(new Cesium.GroundPrimitive({
       *   geometryInstances : rectangleInstance
       * }));
       *
       * // Example 2: Batch instances
       * var color = new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5); // Both instances must have the same color.
       * var rectangleInstance = new Cesium.GeometryInstance({
       *   geometry : new Cesium.RectangleGeometry({
       *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
       *   }),
       *   id : 'rectangle',
       *   attributes : {
       *     color : color
       *   }
       * });
       * var ellipseInstance = new Cesium.GeometryInstance({
       *     geometry : new Cesium.EllipseGeometry({
       *         center : Cesium.Cartesian3.fromDegrees(-105.0, 40.0),
       *         semiMinorAxis : 300000.0,
       *         semiMajorAxis : 400000.0
       *     }),
       *     id : 'ellipse',
       *     attributes : {
       *         color : color
       *     }
       * });
       * scene.primitives.add(new Cesium.GroundPrimitive({
       *   geometryInstances : [rectangleInstance, ellipseInstance]
       * }));
       *
       * @see Primitive
       * @see ClassificationPrimitive
       * @see GeometryInstance
       * @see Appearance
       */
      function GroundPrimitive(options) {
          options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  
          /**
           * The geometry instance rendered with this primitive.  This may
           * be <code>undefined</code> if <code>options.releaseGeometryInstances</code>
           * is <code>true</code> when the primitive is constructed.
           * <p>
           * Changing this property after the primitive is rendered has no effect.
           * </p>
           * <p>
           * Because of the rendering technique used, all geometry instances must be the same color.
           * If there is an instance with a differing color, a <code>DeveloperError</code> will be thrown
           * on the first attempt to render.
           * </p>
           *
           * @readonly
           * @type {Array|GeometryInstance}
           *
           * @default undefined
           */
          this.geometryInstances = options.geometryInstances;
          /**
           * Determines if the primitive will be shown.  This affects all geometry
           * instances in the primitive.
           *
           * @type {Boolean}
           *
           * @default true
           */
          this.show = defaultValue(options.show, true);
          /**
           * Determines whether terrain, 3D Tiles or both will be classified.
           *
           * @type {ClassificationType}
           *
           * @default ClassificationType.BOTH
           */
          this.classificationType = defaultValue(options.classificationType, ClassificationType.BOTH);
          /**
           * This property is for debugging only; it is not for production use nor is it optimized.
           * <p>
           * Draws the bounding sphere for each draw command in the primitive.
           * </p>
           *
           * @type {Boolean}
           *
           * @default false
           */
          this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
  
          /**
           * This property is for debugging only; it is not for production use nor is it optimized.
           * <p>
           * Draws the shadow volume for each geometry in the primitive.
           * </p>
           *
           * @type {Boolean}
           *
           * @default false
           */
          this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false);
  
          this._boundingVolumes = [];
          this._boundingVolumes2D = [];
  
          this._ready = false;
          this._readyPromise = when.defer();
  
          this._primitive = undefined;
  
          this._maxHeight = undefined;
          this._minHeight = undefined;
  
          this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
          this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
  
          this._boundingSpheresKeys = [];
          this._boundingSpheres = [];
  
          var that = this;
          this._primitiveOptions = {
              geometryInstances : undefined,
              vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false),
              interleave : defaultValue(options.interleave, false),
              releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true),
              allowPicking : defaultValue(options.allowPicking, true),
              asynchronous : defaultValue(options.asynchronous, true),
              compressVertices : defaultValue(options.compressVertices, true),
              _createBoundingVolumeFunction : undefined,
              _updateAndQueueCommandsFunction : undefined,
              _pickPrimitive : that,
              _extruded : true,
              _uniformMap : GroundPrimitiveUniformMap
          };
      }
  
      defineProperties(GroundPrimitive.prototype, {
          /**
           * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default true
           */
          vertexCacheOptimize : {
              get : function() {
                  return this._primitiveOptions.vertexCacheOptimize;
              }
          },
  
          /**
           * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default false
           */
          interleave : {
              get : function() {
                  return this._primitiveOptions.interleave;
              }
          },
  
          /**
           * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default true
           */
          releaseGeometryInstances : {
              get : function() {
                  return this._primitiveOptions.releaseGeometryInstances;
              }
          },
  
          /**
           * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default true
           */
          allowPicking : {
              get : function() {
                  return this._primitiveOptions.allowPicking;
              }
          },
  
          /**
           * Determines if the geometry instances will be created and batched on a web worker.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default true
           */
          asynchronous : {
              get : function() {
                  return this._primitiveOptions.asynchronous;
              }
          },
  
          /**
           * When <code>true</code>, geometry vertices are compressed, which will save memory.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           *
           * @default true
           */
          compressVertices : {
              get : function() {
                  return this._primitiveOptions.compressVertices;
              }
          },
  
          /**
           * Determines if the primitive is complete and ready to render.  If this property is
           * true, the primitive will be rendered the next time that {@link GroundPrimitive#update}
           * is called.
           *
           * @memberof GroundPrimitive.prototype
           *
           * @type {Boolean}
           * @readonly
           */
          ready : {
              get : function() {
                  return this._ready;
              }
          },
  
          /**
           * Gets a promise that resolves when the primitive is ready to render.
           * @memberof GroundPrimitive.prototype
           * @type {Promise.<GroundPrimitive>}
           * @readonly
           */
          readyPromise : {
              get : function() {
                  return this._readyPromise.promise;
              }
          }
      });
  
      /**
       * Determines if GroundPrimitive rendering is supported.
       *
       * @param {Scene} scene The scene.
       * @returns {Boolean} <code>true</code> if GroundPrimitives are supported; otherwise, returns <code>false</code>
       */
      GroundPrimitive.isSupported = ClassificationPrimitive.isSupported;
  
      GroundPrimitive._defaultMaxTerrainHeight = 9000.0;
      GroundPrimitive._defaultMinTerrainHeight = -100000.0;
  
      GroundPrimitive._terrainHeights = undefined;
      GroundPrimitive._terrainHeightsMaxLevel = 6;
  
      function getComputeMaximumHeightFunction(primitive) {
          return function(granularity, ellipsoid) {
              var r = ellipsoid.maximumRadius;
              var delta = (r / Math.cos(granularity * 0.5)) - r;
              return primitive._maxHeight + delta;
          };
      }
  
      function getComputeMinimumHeightFunction(primitive) {
          return function(granularity, ellipsoid) {
              return primitive._minHeight;
          };
      }
  
      var scratchBVCartesianHigh = new Cartesian3();
      var scratchBVCartesianLow = new Cartesian3();
      var scratchBVCartesian = new Cartesian3();
      var scratchBVCartographic = new Cartographic();
      var scratchBVRectangle = new Rectangle();
      var tilingScheme = new GeographicTilingScheme();
      var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
      var scratchTileXY = new Cartesian2();
  
      function getRectangle(frameState, geometry) {
          var ellipsoid = frameState.mapProjection.ellipsoid;
  
          if (!defined(geometry.attributes) || !defined(geometry.attributes.position3DHigh)) {
              if (defined(geometry.rectangle)) {
                  return geometry.rectangle;
              }
  
              return undefined;
          }
  
          var highPositions = geometry.attributes.position3DHigh.values;
          var lowPositions = geometry.attributes.position3DLow.values;
          var length = highPositions.length;
  
          var minLat = Number.POSITIVE_INFINITY;
          var minLon = Number.POSITIVE_INFINITY;
          var maxLat = Number.NEGATIVE_INFINITY;
          var maxLon = Number.NEGATIVE_INFINITY;
  
          for (var i = 0; i < length; i +=3) {
              var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh);
              var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow);
  
              var position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian);
              var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic);
  
              var latitude = cartographic.latitude;
              var longitude = cartographic.longitude;
  
              minLat = Math.min(minLat, latitude);
              minLon = Math.min(minLon, longitude);
              maxLat = Math.max(maxLat, latitude);
              maxLon = Math.max(maxLon, longitude);
          }
  
          var rectangle = scratchBVRectangle;
          rectangle.north = maxLat;
          rectangle.south = minLat;
          rectangle.east = maxLon;
          rectangle.west = minLon;
  
          return rectangle;
      }
  
      var scratchDiagonalCartesianNE = new Cartesian3();
      var scratchDiagonalCartesianSW = new Cartesian3();
      var scratchDiagonalCartographic = new Cartographic();
      var scratchCenterCartesian = new Cartesian3();
      var scratchSurfaceCartesian = new Cartesian3();
  
      function getTileXYLevel(rectangle) {
          Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]);
          Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]);
          Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]);
          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 = GroundPrimitive._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)
          };
      }
  
      function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
          var xyLevel = getTileXYLevel(rectangle);
  
          // Get the terrain min/max for that tile
          var minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
          var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
          if (defined(xyLevel)) {
              var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
              var heights = GroundPrimitive._terrainHeights[key];
              if (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(Rectangle.northeast(rectangle, scratchDiagonalCartographic),
                  scratchDiagonalCartesianNE);
              ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic),
                  scratchDiagonalCartesianSW);
  
              Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian);
              Cartesian3.add(scratchDiagonalCartesianNE,
                  Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian);
              var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian);
              if (defined(surfacePosition)) {
                  var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition);
                  minTerrainHeight = Math.min(minTerrainHeight, -distance);
              } else {
                  minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
              }
          }
  
          primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight);
          primitive._maxTerrainHeight = maxTerrainHeight;
      }
  
      var scratchBoundingSphere = new BoundingSphere();
      function getInstanceBoundingSphere(rectangle, ellipsoid) {
          var xyLevel = getTileXYLevel(rectangle);
  
          // Get the terrain max for that tile
          var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
          if (defined(xyLevel)) {
              var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
              var heights = GroundPrimitive._terrainHeights[key];
              if (defined(heights)) {
                  maxTerrainHeight = heights[1];
              }
          }
  
          var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
          BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere);
  
          return BoundingSphere.union(result, scratchBoundingSphere, result);
      }
  
      function createBoundingVolume(groundPrimitive, frameState, geometry) {
          var ellipsoid = frameState.mapProjection.ellipsoid;
          var rectangle = getRectangle(frameState, geometry);
  
          // Use an oriented bounding box by default, but switch to a bounding sphere if bounding box creation would fail.
          if (rectangle.width < CesiumMath.PI) {
              var obb = OrientedBoundingBox.fromRectangle(rectangle, groundPrimitive._maxHeight, groundPrimitive._minHeight, ellipsoid);
              groundPrimitive._boundingVolumes.push(obb);
          } else {
              var highPositions = geometry.attributes.position3DHigh.values;
              var lowPositions = geometry.attributes.position3DLow.values;
              groundPrimitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions));
          }
  
          if (!frameState.scene3DOnly) {
              var projection = frameState.mapProjection;
              var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, groundPrimitive._maxHeight, groundPrimitive._minHeight);
              Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
  
              groundPrimitive._boundingVolumes2D.push(boundingVolume);
          }
      }
  
      function boundingVolumeIndex(commandIndex, length) {
          return Math.floor((commandIndex % length) / 3);
      }
  
      function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
          var boundingVolumes;
          if (frameState.mode === SceneMode.SCENE3D) {
              boundingVolumes = groundPrimitive._boundingVolumes;
          } else {
              boundingVolumes = groundPrimitive._boundingVolumes2D;
          }
  
          var pass;
          switch (groundPrimitive.classificationType) {
              case ClassificationType.TERRAIN:
                  pass = Pass.TERRAIN_CLASSIFICATION;
                  break;
              case ClassificationType.CESIUM_3D_TILE:
                  pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
                  break;
              default:
                  pass = Pass.CLASSIFICATION;
          }
  
          var commandList = frameState.commandList;
          var passes = frameState.passes;
          if (passes.render) {
              var colorLength = colorCommands.length;
              var i;
              var colorCommand;
  
              for (i = 0; i < colorLength; ++i) {
                  colorCommand = colorCommands[i];
                  colorCommand.owner = groundPrimitive;
                  colorCommand.modelMatrix = modelMatrix;
                  colorCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)];
                  colorCommand.cull = cull;
                  colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
                  colorCommand.pass = pass;
  
                  commandList.push(colorCommand);
              }
  
              if (frameState.invertClassification) {
                  var ignoreShowCommands = groundPrimitive._primitive._commandsIgnoreShow;
                  var ignoreShowCommandsLength = ignoreShowCommands.length;
  
                  for (i = 0; i < ignoreShowCommandsLength; ++i) {
                      var bvIndex = Math.floor(i / 2);
                      colorCommand = ignoreShowCommands[i];
                      colorCommand.modelMatrix = modelMatrix;
                      colorCommand.boundingVolume = boundingVolumes[bvIndex];
                      colorCommand.cull = cull;
                      colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
  
                      commandList.push(colorCommand);
                  }
              }
          }
  
          if (passes.pick) {
              var pickLength = pickCommands.length;
              var primitive = groundPrimitive._primitive._primitive;
              var pickOffsets = primitive._pickOffsets;
              for (var j = 0; j < pickLength; ++j) {
                  var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)];
                  var bv = boundingVolumes[pickOffset.index];
  
                  var pickCommand = pickCommands[j];
                  pickCommand.owner = groundPrimitive;
                  pickCommand.modelMatrix = modelMatrix;
                  pickCommand.boundingVolume = bv;
                  pickCommand.cull = cull;
                  pickCommand.pass = pass;
  
                  commandList.push(pickCommand);
              }
          }
      }
  
      GroundPrimitive._initialized = false;
      GroundPrimitive._initPromise = undefined;
  
      /**
       * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
       * GroundPrimitive synchronously.
       *
       * @returns {Promise} A promise that will resolve once the terrain heights have been loaded.
       *
       */
      GroundPrimitive.initializeTerrainHeights = function() {
          var initPromise = GroundPrimitive._initPromise;
          if (defined(initPromise)) {
              return initPromise;
          }
  
          GroundPrimitive._initPromise = Resource.fetchJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) {
              GroundPrimitive._initialized = true;
              GroundPrimitive._terrainHeights = json;
          });
  
          return GroundPrimitive._initPromise;
      };
  
      /**
       * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
       * get the draw commands needed to render this primitive.
       * <p>
       * Do not call this function directly.  This is documented just to
       * list the exceptions that may be propagated when the scene is rendered:
       * </p>
       *
       * @exception {DeveloperError} All instance geometries must have the same primitiveType.
       * @exception {DeveloperError} Appearance and material have a uniform with the same name.
       * @exception {DeveloperError} Not all of the geometry instances have the same color attribute.
       */
      GroundPrimitive.prototype.update = function(frameState) {
          if (!defined(this._primitive) && !defined(this.geometryInstances)) {
              return;
          }
  
          if (!GroundPrimitive._initialized) {
              //>>includeStart('debug', pragmas.debug);
              if (!this.asynchronous) {
                  throw new DeveloperError('For synchronous GroundPrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.');
              }
              //>>includeEnd('debug');
  
              GroundPrimitive.initializeTerrainHeights();
              return;
          }
  
          var that = this;
          var primitiveOptions = this._primitiveOptions;
  
          if (!defined(this._primitive)) {
              var ellipsoid = frameState.mapProjection.ellipsoid;
  
              var instance;
              var geometry;
              var instanceType;
  
              var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances];
              var length = instances.length;
              var groundInstances = new Array(length);
  
              var i;
              var rectangle;
              for (i = 0; i < length; ++i) {
                  instance = instances[i];
                  geometry = instance.geometry;
                  var instanceRectangle = getRectangle(frameState, geometry);
                  if (!defined(rectangle)) {
                      rectangle = instanceRectangle;
                  } else if (defined(instanceRectangle)) {
                      Rectangle.union(rectangle, instanceRectangle, rectangle);
                  }
  
                  var id = instance.id;
                  if (defined(id) && defined(instanceRectangle)) {
                      var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid);
                      this._boundingSpheresKeys.push(id);
                      this._boundingSpheres.push(boundingSphere);
                  }
  
                  instanceType = geometry.constructor;
                  if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) {
                      //>>includeStart('debug', pragmas.debug);
                      throw new DeveloperError('Not all of the geometry instances have GroundPrimitive support.');
                      //>>includeEnd('debug');
                  }
              }
  
              // Now compute the min/max heights for the primitive
              setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid);
              var exaggeration = frameState.terrainExaggeration;
              this._minHeight = this._minTerrainHeight * exaggeration;
              this._maxHeight = this._maxTerrainHeight * exaggeration;
  
              for (i = 0; i < length; ++i) {
                  instance = instances[i];
                  geometry = instance.geometry;
                  instanceType = geometry.constructor;
                  groundInstances[i] = new GeometryInstance({
                      geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this),
                          getComputeMaximumHeightFunction(this)),
                      attributes : instance.attributes,
                      id : instance.id
                  });
              }
  
              primitiveOptions.geometryInstances = groundInstances;
  
              primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) {
                  createBoundingVolume(that, frameState, geometry);
              };
              primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
                  updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses);
              };
  
              this._primitive = new ClassificationPrimitive(primitiveOptions);
              this._primitive.readyPromise.then(function(primitive) {
                  that._ready = true;
  
                  if (that.releaseGeometryInstances) {
                      that.geometryInstances = undefined;
                  }
  
                  var error = primitive._error;
                  if (!defined(error)) {
                      that._readyPromise.resolve(that);
                  } else {
                      that._readyPromise.reject(error);
                  }
              });
          }
  
          this._primitive.show = this.show;
          this._primitive.debugShowShadowVolume = this.debugShowShadowVolume;
          this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
          this._primitive.update(frameState);
      };
  
      /**
       * @private
       */
      GroundPrimitive.prototype.getBoundingSphere = function(id) {
          var index = this._boundingSpheresKeys.indexOf(id);
          if (index !== -1) {
              return this._boundingSpheres[index];
          }
  
          return undefined;
      };
  
      /**
       * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
       *
       * @param {Object} id The id of the {@link GeometryInstance}.
       * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
       *
       * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
       *
       * @example
       * var attributes = primitive.getGeometryInstanceAttributes('an id');
       * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
       * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
       */
      GroundPrimitive.prototype.getGeometryInstanceAttributes = function(id) {
          //>>includeStart('debug', pragmas.debug);
          if (!defined(this._primitive)) {
              throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
          }
          //>>includeEnd('debug');
          return this._primitive.getGeometryInstanceAttributes(id);
      };
  
      /**
       * Returns true if this object was destroyed; otherwise, false.
       * <p>
       * If this object was destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
       * </p>
       *
       * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
       *
       * @see GroundPrimitive#destroy
       */
      GroundPrimitive.prototype.isDestroyed = function() {
          return false;
      };
  
      /**
       * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
       * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
       * <p>
       * Once an object is destroyed, it should not be used; calling any function other than
       * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
       * assign the return value (<code>undefined</code>) to the object as done in the example.
       * </p>
       *
       * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
       *
       * @example
       * e = e && e.destroy();
       *
       * @see GroundPrimitive#isDestroyed
       */
      GroundPrimitive.prototype.destroy = function() {
          this._primitive = this._primitive && this._primitive.destroy();
          return destroyObject(this);
      };
  
      return GroundPrimitive;
  });
  