text: define([
          './arrayFill',
          './arrayRemoveDuplicates',
          './BoundingSphere',
          './Cartesian3',
          './Cartographic',
          './Check',
          './ComponentDatatype',
          './CornerType',
          './CorridorGeometryLibrary',
          './defaultValue',
          './defined',
          './defineProperties',
          './Ellipsoid',
          './Geometry',
          './GeometryAttribute',
          './GeometryAttributes',
          './GeometryOffsetAttribute',
          './IndexDatatype',
          './Math',
          './PolygonPipeline',
          './PrimitiveType',
          './Rectangle',
          './VertexFormat'
      ], function(
          arrayFill,
          arrayRemoveDuplicates,
          BoundingSphere,
          Cartesian3,
          Cartographic,
          Check,
          ComponentDatatype,
          CornerType,
          CorridorGeometryLibrary,
          defaultValue,
          defined,
          defineProperties,
          Ellipsoid,
          Geometry,
          GeometryAttribute,
          GeometryAttributes,
          GeometryOffsetAttribute,
          IndexDatatype,
          CesiumMath,
          PolygonPipeline,
          PrimitiveType,
          Rectangle,
          VertexFormat) {
      'use strict';
  
      var cartesian1 = new Cartesian3();
      var cartesian2 = new Cartesian3();
      var cartesian3 = new Cartesian3();
      var cartesian4 = new Cartesian3();
      var cartesian5 = new Cartesian3();
      var cartesian6 = new Cartesian3();
  
      var scratch1 = new Cartesian3();
      var scratch2 = new Cartesian3();
  
      function scaleToSurface(positions, ellipsoid) {
          for (var i = 0; i < positions.length; i++) {
              positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]);
          }
          return positions;
      }
  
      function addNormals(attr, normal, left, front, back, vertexFormat) {
          var normals = attr.normals;
          var tangents = attr.tangents;
          var bitangents = attr.bitangents;
          var forward = Cartesian3.normalize(Cartesian3.cross(left, normal, scratch1), scratch1);
          if (vertexFormat.normal) {
              CorridorGeometryLibrary.addAttribute(normals, normal, front, back);
          }
          if (vertexFormat.tangent) {
              CorridorGeometryLibrary.addAttribute(tangents, forward, front, back);
          }
          if (vertexFormat.bitangent) {
              CorridorGeometryLibrary.addAttribute(bitangents, left, front, back);
          }
      }
  
      function combine(computedPositions, vertexFormat, ellipsoid) {
          var positions = computedPositions.positions;
          var corners = computedPositions.corners;
          var endPositions = computedPositions.endPositions;
          var computedLefts = computedPositions.lefts;
          var computedNormals = computedPositions.normals;
          var attributes = new GeometryAttributes();
          var corner;
          var leftCount = 0;
          var rightCount = 0;
          var i;
          var indicesLength = 0;
          var length;
          for (i = 0; i < positions.length; i += 2) {
              length = positions[i].length - 3;
              leftCount += length; //subtracting 3 to account for duplicate points at corners
              indicesLength += length*2;
              rightCount += positions[i + 1].length - 3;
          }
          leftCount += 3; //add back count for end positions
          rightCount += 3;
          for (i = 0; i < corners.length; i++) {
              corner = corners[i];
              var leftSide = corners[i].leftPositions;
              if (defined(leftSide)) {
                  length = leftSide.length;
                  leftCount += length;
                  indicesLength += length;
              } else {
                  length = corners[i].rightPositions.length;
                  rightCount += length;
                  indicesLength += length;
              }
          }
  
          var addEndPositions = defined(endPositions);
          var endPositionLength;
          if (addEndPositions) {
              endPositionLength = endPositions[0].length - 3;
              leftCount += endPositionLength;
              rightCount += endPositionLength;
              endPositionLength /= 3;
              indicesLength += endPositionLength * 6;
          }
          var size = leftCount + rightCount;
          var finalPositions = new Float64Array(size);
          var normals = (vertexFormat.normal) ? new Float32Array(size) : undefined;
          var tangents = (vertexFormat.tangent) ? new Float32Array(size) : undefined;
          var bitangents = (vertexFormat.bitangent) ? new Float32Array(size) : undefined;
          var attr = {
              normals : normals,
              tangents : tangents,
              bitangents : bitangents
          };
          var front = 0;
          var back = size - 1;
          var UL, LL, UR, LR;
          var normal = cartesian1;
          var left = cartesian2;
          var rightPos, leftPos;
          var halfLength = endPositionLength / 2;
  
          var indices = IndexDatatype.createTypedArray(size / 3, indicesLength);
          var index = 0;
          if (addEndPositions) { // add rounded end
              leftPos = cartesian3;
              rightPos = cartesian4;
              var firstEndPositions = endPositions[0];
              normal = Cartesian3.fromArray(computedNormals, 0, normal);
              left = Cartesian3.fromArray(computedLefts, 0, left);
              for (i = 0; i < halfLength; i++) {
                  leftPos = Cartesian3.fromArray(firstEndPositions, (halfLength - 1 - i) * 3, leftPos);
                  rightPos = Cartesian3.fromArray(firstEndPositions, (halfLength + i) * 3, rightPos);
                  CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
                  CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
                  addNormals(attr, normal, left, front, back, vertexFormat);
  
                  LL = front / 3;
                  LR = LL + 1;
                  UL = (back - 2) / 3;
                  UR = UL - 1;
                  indices[index++] = UL;
                  indices[index++] = LL;
                  indices[index++] = UR;
                  indices[index++] = UR;
                  indices[index++] = LL;
                  indices[index++] = LR;
  
                  front += 3;
                  back -= 3;
              }
          }
  
          var posIndex = 0;
          var compIndex = 0;
          var rightEdge = positions[posIndex++]; //add first two edges
          var leftEdge = positions[posIndex++];
          finalPositions.set(rightEdge, front);
          finalPositions.set(leftEdge, back - leftEdge.length + 1);
  
          left = Cartesian3.fromArray(computedLefts, compIndex, left);
          var rightNormal;
          var leftNormal;
          length = leftEdge.length - 3;
          for (i = 0; i < length; i += 3) {
              rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, i, scratch1), scratch1);
              leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - i, scratch2), scratch2);
              normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
              addNormals(attr, normal, left, front, back, vertexFormat);
  
              LL = front / 3;
              LR = LL + 1;
              UL = (back - 2) / 3;
              UR = UL - 1;
              indices[index++] = UL;
              indices[index++] = LL;
              indices[index++] = UR;
              indices[index++] = UR;
              indices[index++] = LL;
              indices[index++] = LR;
  
              front += 3;
              back -= 3;
          }
  
          rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, length, scratch1), scratch1);
          leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length, scratch2), scratch2);
          normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
          compIndex += 3;
          for (i = 0; i < corners.length; i++) {
              var j;
              corner = corners[i];
              var l = corner.leftPositions;
              var r = corner.rightPositions;
              var pivot;
              var start;
              var outsidePoint = cartesian6;
              var previousPoint = cartesian3;
              var nextPoint = cartesian4;
              normal = Cartesian3.fromArray(computedNormals, compIndex, normal);
              if (defined(l)) {
                  addNormals(attr, normal, left, undefined, back, vertexFormat);
                  back -= 3;
                  pivot = LR;
                  start = UR;
                  for (j = 0; j < l.length / 3; j++) {
                      outsidePoint = Cartesian3.fromArray(l, j * 3, outsidePoint);
                      indices[index++] = pivot;
                      indices[index++] = start - j - 1;
                      indices[index++] = start - j;
                      CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, undefined, back);
                      previousPoint = Cartesian3.fromArray(finalPositions, (start - j - 1) * 3, previousPoint);
                      nextPoint = Cartesian3.fromArray(finalPositions, pivot * 3, nextPoint);
                      left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
                      addNormals(attr, normal, left, undefined, back, vertexFormat);
                      back -= 3;
                  }
                  outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
                  previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start) * 3, previousPoint), outsidePoint, previousPoint);
                  nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start - j) * 3, nextPoint), outsidePoint, nextPoint);
                  left = Cartesian3.normalize(Cartesian3.add(previousPoint, nextPoint, left), left);
                  addNormals(attr, normal, left, front, undefined, vertexFormat);
                  front += 3;
              } else {
                  addNormals(attr, normal, left, front, undefined, vertexFormat);
                  front += 3;
                  pivot = UR;
                  start = LR;
                  for (j = 0; j < r.length / 3; j++) {
                      outsidePoint = Cartesian3.fromArray(r, j * 3, outsidePoint);
                      indices[index++] = pivot;
                      indices[index++] = start + j;
                      indices[index++] = start + j + 1;
                      CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, front);
                      previousPoint = Cartesian3.fromArray(finalPositions, pivot * 3, previousPoint);
                      nextPoint = Cartesian3.fromArray(finalPositions, (start + j) * 3, nextPoint);
                      left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
                      addNormals(attr, normal, left, front, undefined, vertexFormat);
                      front += 3;
                  }
                  outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
                  previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start + j) * 3, previousPoint), outsidePoint, previousPoint);
                  nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, start * 3, nextPoint), outsidePoint, nextPoint);
                  left = Cartesian3.normalize(Cartesian3.negate(Cartesian3.add(nextPoint, previousPoint, left), left), left);
                  addNormals(attr, normal, left, undefined, back, vertexFormat);
                  back -= 3;
              }
              rightEdge = positions[posIndex++];
              leftEdge = positions[posIndex++];
              rightEdge.splice(0, 3); //remove duplicate points added by corner
              leftEdge.splice(leftEdge.length - 3, 3);
              finalPositions.set(rightEdge, front);
              finalPositions.set(leftEdge, back - leftEdge.length + 1);
              length = leftEdge.length - 3;
  
              compIndex += 3;
              left = Cartesian3.fromArray(computedLefts, compIndex, left);
              for (j = 0; j < leftEdge.length; j += 3) {
                  rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, j, scratch1), scratch1);
                  leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - j, scratch2), scratch2);
                  normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
                  addNormals(attr, normal, left, front, back, vertexFormat);
  
                  LR = front / 3;
                  LL = LR - 1;
                  UR = (back - 2) / 3;
                  UL = UR + 1;
                  indices[index++] = UL;
                  indices[index++] = LL;
                  indices[index++] = UR;
                  indices[index++] = UR;
                  indices[index++] = LL;
                  indices[index++] = LR;
  
                  front += 3;
                  back -= 3;
              }
              front -= 3;
              back += 3;
          }
          normal = Cartesian3.fromArray(computedNormals, computedNormals.length - 3, normal);
          addNormals(attr, normal, left, front, back, vertexFormat);
  
          if (addEndPositions) { // add rounded end
              front += 3;
              back -= 3;
              leftPos = cartesian3;
              rightPos = cartesian4;
              var lastEndPositions = endPositions[1];
              for (i = 0; i < halfLength; i++) {
                  leftPos = Cartesian3.fromArray(lastEndPositions, (endPositionLength - i - 1) * 3, leftPos);
                  rightPos = Cartesian3.fromArray(lastEndPositions, i * 3, rightPos);
                  CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
                  CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
                  addNormals(attr, normal, left, front, back, vertexFormat);
  
                  LR = front / 3;
                  LL = LR - 1;
                  UR = (back - 2) / 3;
                  UL = UR + 1;
                  indices[index++] = UL;
                  indices[index++] = LL;
                  indices[index++] = UR;
                  indices[index++] = UR;
                  indices[index++] = LL;
                  indices[index++] = LR;
  
                  front += 3;
                  back -= 3;
              }
          }
  
          attributes.position = new GeometryAttribute({
              componentDatatype : ComponentDatatype.DOUBLE,
              componentsPerAttribute : 3,
              values : finalPositions
          });
  
          if (vertexFormat.st) {
              var st = new Float32Array(size / 3 * 2);
              var rightSt;
              var leftSt;
              var stIndex = 0;
              if (addEndPositions) {
                  leftCount /= 3;
                  rightCount /= 3;
                  var theta = Math.PI / (endPositionLength + 1);
                  leftSt = 1 / (leftCount - endPositionLength + 1);
                  rightSt = 1 / (rightCount - endPositionLength + 1);
                  var a;
                  var halfEndPos = endPositionLength / 2;
                  for (i = halfEndPos + 1; i < endPositionLength + 1; i++) { // lower left rounded end
                      a = CesiumMath.PI_OVER_TWO + theta * i;
                      st[stIndex++] = rightSt * (1 + Math.cos(a));
                      st[stIndex++] = 0.5 * (1 + Math.sin(a));
                  }
                  for (i = 1; i < rightCount - endPositionLength + 1; i++) { // bottom edge
                      st[stIndex++] = i * rightSt;
                      st[stIndex++] = 0;
                  }
                  for (i = endPositionLength; i > halfEndPos; i--) { // lower right rounded end
                      a = CesiumMath.PI_OVER_TWO - i * theta;
                      st[stIndex++] = 1 - rightSt * (1 + Math.cos(a));
                      st[stIndex++] = 0.5 * (1 + Math.sin(a));
                  }
                  for (i = halfEndPos; i > 0; i--) { // upper right rounded end
                      a = CesiumMath.PI_OVER_TWO - theta * i;
                      st[stIndex++] = 1 - leftSt * (1 + Math.cos(a));
                      st[stIndex++] = 0.5 * (1 + Math.sin(a));
                  }
                  for (i = leftCount - endPositionLength; i > 0; i--) { // top edge
                      st[stIndex++] = i * leftSt;
                      st[stIndex++] = 1;
                  }
                  for (i = 1; i < halfEndPos + 1; i++) { // upper left rounded end
                      a = CesiumMath.PI_OVER_TWO + theta * i;
                      st[stIndex++] = leftSt * (1 + Math.cos(a));
                      st[stIndex++] = 0.5 * (1 + Math.sin(a));
                  }
              } else {
                  leftCount /= 3;
                  rightCount /= 3;
                  leftSt = 1 / (leftCount - 1);
                  rightSt = 1 / (rightCount - 1);
                  for (i = 0; i < rightCount; i++) { // bottom edge
                      st[stIndex++] = i * rightSt;
                      st[stIndex++] = 0;
                  }
                  for (i = leftCount; i > 0; i--) { // top edge
                      st[stIndex++] = (i - 1) * leftSt;
                      st[stIndex++] = 1;
                  }
              }
  
              attributes.st = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.FLOAT,
                  componentsPerAttribute : 2,
                  values : st
              });
          }
  
          if (vertexFormat.normal) {
              attributes.normal = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.FLOAT,
                  componentsPerAttribute : 3,
                  values : attr.normals
              });
          }
  
          if (vertexFormat.tangent) {
              attributes.tangent = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.FLOAT,
                  componentsPerAttribute : 3,
                  values : attr.tangents
              });
          }
  
          if (vertexFormat.bitangent) {
              attributes.bitangent = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.FLOAT,
                  componentsPerAttribute : 3,
                  values : attr.bitangents
              });
          }
  
          return {
              attributes : attributes,
              indices : indices
          };
      }
  
      function extrudedAttributes(attributes, vertexFormat) {
          if (!vertexFormat.normal && !vertexFormat.tangent && !vertexFormat.bitangent && !vertexFormat.st) {
              return attributes;
          }
          var positions = attributes.position.values;
          var topNormals;
          var topBitangents;
          if (vertexFormat.normal || vertexFormat.bitangent) {
              topNormals = attributes.normal.values;
              topBitangents = attributes.bitangent.values;
          }
          var size = attributes.position.values.length / 18;
          var threeSize = size * 3;
          var twoSize = size * 2;
          var sixSize = threeSize * 2;
          var i;
          if (vertexFormat.normal || vertexFormat.bitangent || vertexFormat.tangent) {
              var normals = (vertexFormat.normal) ? new Float32Array(threeSize * 6) : undefined;
              var tangents = (vertexFormat.tangent) ? new Float32Array(threeSize * 6) : undefined;
              var bitangents = (vertexFormat.bitangent) ? new Float32Array(threeSize * 6) : undefined;
              var topPosition = cartesian1;
              var bottomPosition = cartesian2;
              var previousPosition = cartesian3;
              var normal = cartesian4;
              var tangent = cartesian5;
              var bitangent = cartesian6;
              var attrIndex = sixSize;
              for (i = 0; i < threeSize; i += 3) {
                  var attrIndexOffset = attrIndex + sixSize;
                  topPosition      = Cartesian3.fromArray(positions, i, topPosition);
                  bottomPosition   = Cartesian3.fromArray(positions, i + threeSize, bottomPosition);
                  previousPosition = Cartesian3.fromArray(positions, (i + 3) % threeSize, previousPosition);
                  bottomPosition   = Cartesian3.subtract(bottomPosition,   topPosition, bottomPosition);
                  previousPosition = Cartesian3.subtract(previousPosition, topPosition, previousPosition);
                  normal = Cartesian3.normalize(Cartesian3.cross(bottomPosition, previousPosition, normal), normal);
                  if (vertexFormat.normal) {
                      CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset);
                      CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset + 3);
                      CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex);
                      CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex + 3);
                  }
                  if (vertexFormat.tangent || vertexFormat.bitangent) {
                      bitangent = Cartesian3.fromArray(topNormals, i, bitangent);
                      if (vertexFormat.bitangent) {
                          CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndexOffset);
                          CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndexOffset + 3);
                          CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndex);
                          CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndex + 3);
                      }
  
                      if (vertexFormat.tangent) {
                          tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent);
                          CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset);
                          CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset + 3);
                          CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex);
                          CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex + 3);
                      }
                  }
                  attrIndex += 6;
              }
  
              if (vertexFormat.normal) {
                  normals.set(topNormals); //top
                  for (i = 0; i < threeSize; i += 3) { //bottom normals
                      normals[i + threeSize] = -topNormals[i];
                      normals[i + threeSize + 1] = -topNormals[i + 1];
                      normals[i + threeSize + 2] = -topNormals[i + 2];
                  }
                  attributes.normal.values = normals;
              } else {
                  attributes.normal = undefined;
              }
  
              if (vertexFormat.bitangent) {
                  bitangents.set(topBitangents); //top
                  bitangents.set(topBitangents, threeSize); //bottom
                  attributes.bitangent.values = bitangents;
              } else {
                  attributes.bitangent = undefined;
              }
  
              if (vertexFormat.tangent) {
                  var topTangents = attributes.tangent.values;
                  tangents.set(topTangents); //top
                  tangents.set(topTangents, threeSize); //bottom
                  attributes.tangent.values = tangents;
              }
          }
          if (vertexFormat.st) {
              var topSt = attributes.st.values;
              var st = new Float32Array(twoSize * 6);
              st.set(topSt); //top
              st.set(topSt, twoSize); //bottom
              var index = twoSize * 2;
  
              for ( var j = 0; j < 2; j++) {
                  st[index++] = topSt[0];
                  st[index++] = topSt[1];
                  for (i = 2; i < twoSize; i += 2) {
                      var s = topSt[i];
                      var t = topSt[i + 1];
                      st[index++] = s;
                      st[index++] = t;
                      st[index++] = s;
                      st[index++] = t;
                  }
                  st[index++] = topSt[0];
                  st[index++] = topSt[1];
              }
              attributes.st.values = st;
          }
  
          return attributes;
      }
  
      function addWallPositions(positions, index, wallPositions) {
          wallPositions[index++] = positions[0];
          wallPositions[index++] = positions[1];
          wallPositions[index++] = positions[2];
          for ( var i = 3; i < positions.length; i += 3) {
              var x = positions[i];
              var y = positions[i + 1];
              var z = positions[i + 2];
              wallPositions[index++] = x;
              wallPositions[index++] = y;
              wallPositions[index++] = z;
              wallPositions[index++] = x;
              wallPositions[index++] = y;
              wallPositions[index++] = z;
          }
          wallPositions[index++] = positions[0];
          wallPositions[index++] = positions[1];
          wallPositions[index++] = positions[2];
  
          return wallPositions;
      }
  
      function computePositionsExtruded(params, vertexFormat) {
          var topVertexFormat = new VertexFormat({
              position : vertexFormat.position,
              normal : (vertexFormat.normal || vertexFormat.bitangent || params.shadowVolume),
              tangent : vertexFormat.tangent,
              bitangent : (vertexFormat.normal || vertexFormat.bitangent),
              st : vertexFormat.st
          });
          var ellipsoid = params.ellipsoid;
          var computedPositions = CorridorGeometryLibrary.computePositions(params);
          var attr = combine(computedPositions, topVertexFormat, ellipsoid);
          var height = params.height;
          var extrudedHeight = params.extrudedHeight;
          var attributes = attr.attributes;
          var indices = attr.indices;
          var positions = attributes.position.values;
          var length = positions.length;
          var newPositions = new Float64Array(length * 6);
          var extrudedPositions = new Float64Array(length);
          extrudedPositions.set(positions);
          var wallPositions = new Float64Array(length * 4);
  
          positions = PolygonPipeline.scaleToGeodeticHeight(positions, height, ellipsoid);
          wallPositions = addWallPositions(positions, 0, wallPositions);
          extrudedPositions = PolygonPipeline.scaleToGeodeticHeight(extrudedPositions, extrudedHeight, ellipsoid);
          wallPositions = addWallPositions(extrudedPositions, length * 2, wallPositions);
          newPositions.set(positions);
          newPositions.set(extrudedPositions, length);
          newPositions.set(wallPositions, length * 2);
          attributes.position.values = newPositions;
  
          attributes = extrudedAttributes(attributes, vertexFormat);
          var i;
          var size = length / 3;
          if (params.shadowVolume) {
              var topNormals = attributes.normal.values;
              length = topNormals.length;
  
              var extrudeNormals = new Float32Array(length * 6);
              for (i = 0; i < length; i ++) {
                  topNormals[i] = -topNormals[i];
              }
              //only get normals for bottom layer that's going to be pushed down
              extrudeNormals.set(topNormals, length); //bottom face
              extrudeNormals = addWallPositions(topNormals, length*4, extrudeNormals); //bottom wall
              attributes.extrudeDirection = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.FLOAT,
                  componentsPerAttribute : 3,
                  values : extrudeNormals
              });
              if (!vertexFormat.normal) {
                  attributes.normal = undefined;
              }
          }
          if (defined(params.offsetAttribute)) {
              var applyOffset = new Uint8Array(size * 6);
              if (params.offsetAttribute === GeometryOffsetAttribute.TOP) {
                  applyOffset = arrayFill(applyOffset, 1, 0, size); // top face
                  applyOffset = arrayFill(applyOffset, 1, size*2, size * 4); // top wall
              } else {
                  var applyOffsetValue = params.offsetAttribute === GeometryOffsetAttribute.NONE ? 0 : 1;
                  applyOffset = arrayFill(applyOffset, applyOffsetValue);
              }
              attributes.applyOffset = new GeometryAttribute({
                  componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
                  componentsPerAttribute : 1,
                  values: applyOffset
              });
          }
  
          var iLength = indices.length;
          var twoSize = size + size;
          var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, iLength * 2 + twoSize * 3);
          newIndices.set(indices);
          var index = iLength;
          for (i = 0; i < iLength; i += 3) { // bottom indices
              var v0 = indices[i];
              var v1 = indices[i + 1];
              var v2 = indices[i + 2];
              newIndices[index++] = v2 + size;
              newIndices[index++] = v1 + size;
              newIndices[index++] = v0 + size;
          }
  
          var UL, LL, UR, LR;
  
          for (i = 0; i < twoSize; i += 2) { //wall indices
              UL = i + twoSize;
              LL = UL + twoSize;
              UR = UL + 1;
              LR = LL + 1;
              newIndices[index++] = UL;
              newIndices[index++] = LL;
              newIndices[index++] = UR;
              newIndices[index++] = UR;
              newIndices[index++] = LL;
              newIndices[index++] = LR;
          }
  
          return {
              attributes : attributes,
              indices : newIndices
          };
      }
  
      var scratchCartesian1 = new Cartesian3();
      var scratchCartesian2 = new Cartesian3();
      var scratchCartographic = new Cartographic();
  
      function computeOffsetPoints(position1, position2, ellipsoid, halfWidth, min, max) {
          // Compute direction of offset the point
          var direction = Cartesian3.subtract(position2, position1, scratchCartesian1);
          Cartesian3.normalize(direction, direction);
          var normal = ellipsoid.geodeticSurfaceNormal(position1, scratchCartesian2);
          var offsetDirection = Cartesian3.cross(direction, normal, scratchCartesian1);
          Cartesian3.multiplyByScalar(offsetDirection, halfWidth, offsetDirection);
  
          var minLat = min.latitude;
          var minLon = min.longitude;
          var maxLat = max.latitude;
          var maxLon = max.longitude;
  
          // Compute 2 offset points
          Cartesian3.add(position1, offsetDirection, scratchCartesian2);
          ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);
  
          var lat = scratchCartographic.latitude;
          var lon = scratchCartographic.longitude;
          minLat = Math.min(minLat, lat);
          minLon = Math.min(minLon, lon);
          maxLat = Math.max(maxLat, lat);
          maxLon = Math.max(maxLon, lon);
  
          Cartesian3.subtract(position1, offsetDirection, scratchCartesian2);
          ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);
  
          lat = scratchCartographic.latitude;
          lon = scratchCartographic.longitude;
          minLat = Math.min(minLat, lat);
          minLon = Math.min(minLon, lon);
          maxLat = Math.max(maxLat, lat);
          maxLon = Math.max(maxLon, lon);
  
          min.latitude = minLat;
          min.longitude = minLon;
          max.latitude = maxLat;
          max.longitude = maxLon;
      }
  
      var scratchCartesianOffset = new Cartesian3();
      var scratchCartesianEnds = new Cartesian3();
      var scratchCartographicMin = new Cartographic();
      var scratchCartographicMax = new Cartographic();
  
      function computeRectangle(positions, ellipsoid, width, cornerType, result) {
          positions = scaleToSurface(positions, ellipsoid);
          var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
          var length = cleanPositions.length;
          if (length < 2 || width <= 0) {
              return new Rectangle();
          }
          var halfWidth = width * 0.5;
  
          scratchCartographicMin.latitude = Number.POSITIVE_INFINITY;
          scratchCartographicMin.longitude = Number.POSITIVE_INFINITY;
          scratchCartographicMax.latitude = Number.NEGATIVE_INFINITY;
          scratchCartographicMax.longitude = Number.NEGATIVE_INFINITY;
  
          var lat, lon;
          if (cornerType === CornerType.ROUNDED) {
              // Compute start cap
              var first = cleanPositions[0];
              Cartesian3.subtract(first, cleanPositions[1], scratchCartesianOffset);
              Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
              Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
              Cartesian3.add(first, scratchCartesianOffset, scratchCartesianEnds);
  
              ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
              lat = scratchCartographic.latitude;
              lon = scratchCartographic.longitude;
              scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
              scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
              scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
              scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
          }
  
          // Compute the rest
          for (var i = 0; i < length-1; ++i) {
              computeOffsetPoints(cleanPositions[i], cleanPositions[i+1], ellipsoid, halfWidth,
                  scratchCartographicMin, scratchCartographicMax);
          }
  
          // Compute ending point
          var last = cleanPositions[length-1];
          Cartesian3.subtract(last, cleanPositions[length-2], scratchCartesianOffset);
          Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
          Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
          Cartesian3.add(last, scratchCartesianOffset, scratchCartesianEnds);
          computeOffsetPoints(last, scratchCartesianEnds, ellipsoid, halfWidth,
              scratchCartographicMin, scratchCartographicMax);
  
          if (cornerType === CornerType.ROUNDED) {
              // Compute end cap
              ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
              lat = scratchCartographic.latitude;
              lon = scratchCartographic.longitude;
              scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
              scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
              scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
              scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
          }
  
          var rectangle = defined(result) ? result : new Rectangle();
          rectangle.north = scratchCartographicMax.latitude;
          rectangle.south = scratchCartographicMin.latitude;
          rectangle.east = scratchCartographicMax.longitude;
          rectangle.west = scratchCartographicMin.longitude;
  
          return rectangle;
      }
  
      /**
       * A description of a corridor. Corridor geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
       *
       * @alias CorridorGeometry
       * @constructor
       *
       * @param {Object} options Object with the following properties:
       * @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor.
       * @param {Number} options.width The distance between the edges of the corridor in meters.
       * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
       * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
       * @param {Number} [options.height=0] The distance in meters between the ellipsoid surface and the positions.
       * @param {Number} [options.extrudedHeight] The distance in meters between the ellipsoid surface and the extruded face.
       * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
       * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
       *
       * @see CorridorGeometry.createGeometry
       * @see Packable
       *
       * @demo {@link https://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo}
       *
       * @example
       * var corridor = new Cesium.CorridorGeometry({
       *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
       *   positions : Cesium.Cartesian3.fromDegreesArray([-72.0, 40.0, -70.0, 35.0]),
       *   width : 100000
       * });
       */
      function CorridorGeometry(options) {
          options = defaultValue(options, defaultValue.EMPTY_OBJECT);
          var positions = options.positions;
          var width = options.width;
  
          //>>includeStart('debug', pragmas.debug);
          Check.defined('options.positions', positions);
          Check.defined('options.width', width);
          //>>includeEnd('debug');
  
          var height = defaultValue(options.height, 0.0);
          var extrudedHeight = defaultValue(options.extrudedHeight, height);
  
          this._positions = positions;
          this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
          this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
          this._width = width;
          this._height = Math.max(height, extrudedHeight);
          this._extrudedHeight = Math.min(height, extrudedHeight);
          this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
          this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
          this._shadowVolume = defaultValue(options.shadowVolume, false);
          this._workerName = 'createCorridorGeometry';
          this._offsetAttribute = options.offsetAttribute;
          this._rectangle = undefined;
  
          /**
           * The number of elements used to pack the object into an array.
           * @type {Number}
           */
          this.packedLength = 1 + positions.length * Cartesian3.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + 7;
      }
  
      /**
       * Stores the provided instance into the provided array.
       *
       * @param {CorridorGeometry} 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
       */
      CorridorGeometry.pack = function(value, array, startingIndex) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('value', value);
          Check.defined('array', array);
          //>>includeEnd('debug');
  
          startingIndex = defaultValue(startingIndex, 0);
  
          var positions = value._positions;
          var length = positions.length;
          array[startingIndex++] = length;
  
          for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
              Cartesian3.pack(positions[i], array, startingIndex);
          }
  
          Ellipsoid.pack(value._ellipsoid, array, startingIndex);
          startingIndex += Ellipsoid.packedLength;
  
          VertexFormat.pack(value._vertexFormat, array, startingIndex);
          startingIndex += VertexFormat.packedLength;
  
          array[startingIndex++] = value._width;
          array[startingIndex++] = value._height;
          array[startingIndex++] = value._extrudedHeight;
          array[startingIndex++] = value._cornerType;
          array[startingIndex++] = value._granularity;
          array[startingIndex++] = value._shadowVolume ? 1.0 : 0.0;
          array[startingIndex] = defaultValue(value._offsetAttribute, -1);
  
          return array;
      };
  
      var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
      var scratchVertexFormat = new VertexFormat();
      var scratchOptions = {
          positions : undefined,
          ellipsoid : scratchEllipsoid,
          vertexFormat : scratchVertexFormat,
          width : undefined,
          height : undefined,
          extrudedHeight : undefined,
          cornerType : undefined,
          granularity : undefined,
          shadowVolume: undefined,
          offsetAttribute: undefined
      };
  
      /**
       * 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 {CorridorGeometry} [result] The object into which to store the result.
       * @returns {CorridorGeometry} The modified result parameter or a new CorridorGeometry instance if one was not provided.
       */
      CorridorGeometry.unpack = function(array, startingIndex, result) {
          //>>includeStart('debug', pragmas.debug);
          Check.defined('array', array);
          //>>includeEnd('debug');
  
          startingIndex = defaultValue(startingIndex, 0);
  
          var length = array[startingIndex++];
          var positions = new Array(length);
  
          for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
              positions[i] = Cartesian3.unpack(array, startingIndex);
          }
  
          var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
          startingIndex += Ellipsoid.packedLength;
  
          var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
          startingIndex += VertexFormat.packedLength;
  
          var width = array[startingIndex++];
          var height = array[startingIndex++];
          var extrudedHeight = array[startingIndex++];
          var cornerType = array[startingIndex++];
          var granularity = array[startingIndex++];
          var shadowVolume = array[startingIndex++] === 1.0;
          var offsetAttribute = array[startingIndex];
  
          if (!defined(result)) {
              scratchOptions.positions = positions;
              scratchOptions.width = width;
              scratchOptions.height = height;
              scratchOptions.extrudedHeight = extrudedHeight;
              scratchOptions.cornerType = cornerType;
              scratchOptions.granularity = granularity;
              scratchOptions.shadowVolume = shadowVolume;
              scratchOptions.offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute;
  
              return new CorridorGeometry(scratchOptions);
          }
  
          result._positions = positions;
          result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
          result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
          result._width = width;
          result._height = height;
          result._extrudedHeight = extrudedHeight;
          result._cornerType = cornerType;
          result._granularity = granularity;
          result._shadowVolume = shadowVolume;
          result._offsetAttribute = offsetAttribute === -1 ? undefined : offsetAttribute;
  
          return result;
      };
  
      /**
       * Computes the bounding rectangle given the provided options
       *
       * @param {Object} options Object with the following properties:
       * @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor.
       * @param {Number} options.width The distance between the edges of the corridor in meters.
       * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
       * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
       * @param {Rectangle} [result] An object in which to store the result.
       *
       * @returns {Rectangle} The result rectangle.
       */
      CorridorGeometry.computeRectangle = function(options, result) {
          options = defaultValue(options, defaultValue.EMPTY_OBJECT);
          var positions = options.positions;
          var width = options.width;
  
          //>>includeStart('debug', pragmas.debug);
          Check.defined('options.positions', positions);
          Check.defined('options.width', width);
          //>>includeEnd('debug');
  
          var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
          var cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
  
          return computeRectangle(positions, ellipsoid, width, cornerType, result);
      };
  
      /**
       * Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere.
       *
       * @param {CorridorGeometry} corridorGeometry A description of the corridor.
       * @returns {Geometry|undefined} The computed vertices and indices.
       */
      CorridorGeometry.createGeometry = function(corridorGeometry) {
          var positions = corridorGeometry._positions;
          var width = corridorGeometry._width;
          var ellipsoid = corridorGeometry._ellipsoid;
  
          positions = scaleToSurface(positions, ellipsoid);
          var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
  
          if ((cleanPositions.length < 2) || (width <= 0)) {
              return;
          }
  
          var height = corridorGeometry._height;
          var extrudedHeight = corridorGeometry._extrudedHeight;
          var extrude = !CesiumMath.equalsEpsilon(height, extrudedHeight, 0, CesiumMath.EPSILON2);
  
          var vertexFormat = corridorGeometry._vertexFormat;
          var params = {
              ellipsoid : ellipsoid,
              positions : cleanPositions,
              width : width,
              cornerType : corridorGeometry._cornerType,
              granularity : corridorGeometry._granularity,
              saveAttributes: true
          };
          var attr;
          if (extrude) {
              params.height = height;
              params.extrudedHeight = extrudedHeight;
              params.shadowVolume = corridorGeometry._shadowVolume;
              params.offsetAttribute = corridorGeometry._offsetAttribute;
              attr = computePositionsExtruded(params, vertexFormat);
          } else {
              var computedPositions = CorridorGeometryLibrary.computePositions(params);
              attr = combine(computedPositions, vertexFormat, ellipsoid);
              attr.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(attr.attributes.position.values, height, ellipsoid);
  
              if (defined(corridorGeometry._offsetAttribute)) {
                  var applyOffsetValue = corridorGeometry._offsetAttribute === GeometryOffsetAttribute.NONE ? 0 : 1;
                  var length = attr.attributes.position.values.length;
                  var applyOffset = new Uint8Array(length / 3);
                  arrayFill(applyOffset, applyOffsetValue);
                  attr.attributes.applyOffset = new GeometryAttribute({
                      componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
                      componentsPerAttribute : 1,
                      values: applyOffset
                  });
              }
          }
          var attributes = attr.attributes;
          var boundingSphere = BoundingSphere.fromVertices(attributes.position.values, undefined, 3);
          if (!vertexFormat.position) {
              attr.attributes.position.values = undefined;
          }
  
          return new Geometry({
              attributes : attributes,
              indices : attr.indices,
              primitiveType : PrimitiveType.TRIANGLES,
              boundingSphere : boundingSphere,
              offsetAttribute : corridorGeometry._offsetAttribute
          });
      };
  
      /**
       * @private
       */
      CorridorGeometry.createShadowVolume = function(corridorGeometry, minHeightFunc, maxHeightFunc) {
          var granularity = corridorGeometry._granularity;
          var ellipsoid = corridorGeometry._ellipsoid;
  
          var minHeight = minHeightFunc(granularity, ellipsoid);
          var maxHeight = maxHeightFunc(granularity, ellipsoid);
  
          return new CorridorGeometry({
              positions : corridorGeometry._positions,
              width : corridorGeometry._width,
              cornerType : corridorGeometry._cornerType,
              ellipsoid : ellipsoid,
              granularity : granularity,
              extrudedHeight : minHeight,
              height : maxHeight,
              vertexFormat : VertexFormat.POSITION_ONLY,
              shadowVolume: true
          });
      };
  
      defineProperties(CorridorGeometry.prototype, {
          /**
           * @private
           */
          rectangle : {
              get : function() {
                  if (!defined(this._rectangle)) {
                      this._rectangle = computeRectangle(this._positions, this._ellipsoid, this._width, this._cornerType);
                  }
                  return this._rectangle;
              }
          },
          /**
           * For remapping texture coordinates when rendering CorridorGeometries as GroundPrimitives.
           *
           * Corridors don't support stRotation,
           * so just return the corners of the original system.
           * @private
           */
          textureCoordinateRotationPoints : {
              get : function() {
                  return [0, 0, 0, 1, 1, 0];
              }
          }
      });
  
      return CorridorGeometry;
  });
  