| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 | import AxisAlignedBoundingBox from "../Core/AxisAlignedBoundingBox.js";import BoundingSphere from "../Core/BoundingSphere.js";import Cartesian2 from "../Core/Cartesian2.js";import Cartesian3 from "../Core/Cartesian3.js";import Cartographic from "../Core/Cartographic.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import Ellipsoid from "../Core/Ellipsoid.js";import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";import CesiumMath from "../Core/Math.js";import Matrix4 from "../Core/Matrix4.js";import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";import Rectangle from "../Core/Rectangle.js";import RuntimeError from "../Core/RuntimeError.js";import TerrainEncoding from "../Core/TerrainEncoding.js";import Transforms from "../Core/Transforms.js";import WebMercatorProjection from "../Core/WebMercatorProjection.js";import createTaskProcessorWorker from "./createTaskProcessorWorker.js";const sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;const sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;const sizeOfFloat = Float32Array.BYTES_PER_ELEMENT;const sizeOfDouble = Float64Array.BYTES_PER_ELEMENT;function indexOfEpsilon(arr, elem, elemType) {  elemType = defaultValue(elemType, CesiumMath);  const count = arr.length;  for (let i = 0; i < count; ++i) {    if (elemType.equalsEpsilon(arr[i], elem, CesiumMath.EPSILON12)) {      return i;    }  }  return -1;}function createVerticesFromGoogleEarthEnterpriseBuffer(  parameters,  transferableObjects) {  parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid);  parameters.rectangle = Rectangle.clone(parameters.rectangle);  const statistics = processBuffer(    parameters.buffer,    parameters.relativeToCenter,    parameters.ellipsoid,    parameters.rectangle,    parameters.nativeRectangle,    parameters.exaggeration,    parameters.exaggerationRelativeHeight,    parameters.skirtHeight,    parameters.includeWebMercatorT,    parameters.negativeAltitudeExponentBias,    parameters.negativeElevationThreshold  );  const vertices = statistics.vertices;  transferableObjects.push(vertices.buffer);  const indices = statistics.indices;  transferableObjects.push(indices.buffer);  return {    vertices: vertices.buffer,    indices: indices.buffer,    numberOfAttributes: statistics.encoding.stride,    minimumHeight: statistics.minimumHeight,    maximumHeight: statistics.maximumHeight,    boundingSphere3D: statistics.boundingSphere3D,    orientedBoundingBox: statistics.orientedBoundingBox,    occludeePointInScaledSpace: statistics.occludeePointInScaledSpace,    encoding: statistics.encoding,    vertexCountWithoutSkirts: statistics.vertexCountWithoutSkirts,    indexCountWithoutSkirts: statistics.indexCountWithoutSkirts,    westIndicesSouthToNorth: statistics.westIndicesSouthToNorth,    southIndicesEastToWest: statistics.southIndicesEastToWest,    eastIndicesNorthToSouth: statistics.eastIndicesNorthToSouth,    northIndicesWestToEast: statistics.northIndicesWestToEast,  };}const scratchCartographic = new Cartographic();const scratchCartesian = new Cartesian3();const minimumScratch = new Cartesian3();const maximumScratch = new Cartesian3();const matrix4Scratch = new Matrix4();function processBuffer(  buffer,  relativeToCenter,  ellipsoid,  rectangle,  nativeRectangle,  exaggeration,  exaggerationRelativeHeight,  skirtHeight,  includeWebMercatorT,  negativeAltitudeExponentBias,  negativeElevationThreshold) {  let geographicWest;  let geographicSouth;  let geographicEast;  let geographicNorth;  let rectangleWidth, rectangleHeight;  if (!defined(rectangle)) {    geographicWest = CesiumMath.toRadians(nativeRectangle.west);    geographicSouth = CesiumMath.toRadians(nativeRectangle.south);    geographicEast = CesiumMath.toRadians(nativeRectangle.east);    geographicNorth = CesiumMath.toRadians(nativeRectangle.north);    rectangleWidth = CesiumMath.toRadians(rectangle.width);    rectangleHeight = CesiumMath.toRadians(rectangle.height);  } else {    geographicWest = rectangle.west;    geographicSouth = rectangle.south;    geographicEast = rectangle.east;    geographicNorth = rectangle.north;    rectangleWidth = rectangle.width;    rectangleHeight = rectangle.height;  }  // Keep track of quad borders so we can remove duplicates around the borders  const quadBorderLatitudes = [geographicSouth, geographicNorth];  const quadBorderLongitudes = [geographicWest, geographicEast];  const fromENU = Transforms.eastNorthUpToFixedFrame(    relativeToCenter,    ellipsoid  );  const toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch);  let southMercatorY;  let oneOverMercatorHeight;  if (includeWebMercatorT) {    southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(      geographicSouth    );    oneOverMercatorHeight =      1.0 /      (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) -        southMercatorY);  }  const hasExaggeration = exaggeration !== 1.0;  const includeGeodeticSurfaceNormals = hasExaggeration;  const dv = new DataView(buffer);  let minHeight = Number.POSITIVE_INFINITY;  let maxHeight = Number.NEGATIVE_INFINITY;  const minimum = minimumScratch;  minimum.x = Number.POSITIVE_INFINITY;  minimum.y = Number.POSITIVE_INFINITY;  minimum.z = Number.POSITIVE_INFINITY;  const maximum = maximumScratch;  maximum.x = Number.NEGATIVE_INFINITY;  maximum.y = Number.NEGATIVE_INFINITY;  maximum.z = Number.NEGATIVE_INFINITY;  // Compute sizes  let offset = 0;  let size = 0;  let indicesSize = 0;  let quadSize;  let quad;  for (quad = 0; quad < 4; ++quad) {    let o = offset;    quadSize = dv.getUint32(o, true);    o += sizeOfUint32;    const x = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);    o += sizeOfDouble;    if (indexOfEpsilon(quadBorderLongitudes, x) === -1) {      quadBorderLongitudes.push(x);    }    const y = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);    o += sizeOfDouble;    if (indexOfEpsilon(quadBorderLatitudes, y) === -1) {      quadBorderLatitudes.push(y);    }    o += 2 * sizeOfDouble; // stepX + stepY    let c = dv.getInt32(o, true); // Read point count    o += sizeOfInt32;    size += c;    c = dv.getInt32(o, true); // Read index count    indicesSize += c * 3;    offset += quadSize + sizeOfUint32; // Jump to next quad  }  // Quad Border points to remove duplicates  const quadBorderPoints = [];  const quadBorderIndices = [];  // Create arrays  const positions = new Array(size);  const uvs = new Array(size);  const heights = new Array(size);  const webMercatorTs = includeWebMercatorT ? new Array(size) : [];  const geodeticSurfaceNormals = includeGeodeticSurfaceNormals    ? new Array(size)    : [];  const indices = new Array(indicesSize);  // Points are laid out in rows starting at SW, so storing border points as we  //  come across them all points will be adjacent.  const westBorder = [];  const southBorder = [];  const eastBorder = [];  const northBorder = [];  // Each tile is split into 4 parts  let pointOffset = 0;  let indicesOffset = 0;  offset = 0;  for (quad = 0; quad < 4; ++quad) {    quadSize = dv.getUint32(offset, true);    offset += sizeOfUint32;    const startQuad = offset;    const originX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);    offset += sizeOfDouble;    const originY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);    offset += sizeOfDouble;    const stepX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);    const halfStepX = stepX * 0.5;    offset += sizeOfDouble;    const stepY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);    const halfStepY = stepY * 0.5;    offset += sizeOfDouble;    const numPoints = dv.getInt32(offset, true);    offset += sizeOfInt32;    const numFaces = dv.getInt32(offset, true);    offset += sizeOfInt32;    //const level = dv.getInt32(offset, true);    offset += sizeOfInt32;    // Keep track of quad indices to overall tile indices    const indicesMapping = new Array(numPoints);    for (let i = 0; i < numPoints; ++i) {      const longitude = originX + dv.getUint8(offset++) * stepX;      scratchCartographic.longitude = longitude;      const latitude = originY + dv.getUint8(offset++) * stepY;      scratchCartographic.latitude = latitude;      let height = dv.getFloat32(offset, true);      offset += sizeOfFloat;      // In order to support old clients, negative altitude values are stored as      // height/-2^32. Old clients see the value as really close to 0 but new clients multiply      // by -2^32 to get the real negative altitude value.      if (height !== 0 && height < negativeElevationThreshold) {        height *= -Math.pow(2, negativeAltitudeExponentBias);      }      // Height is stored in units of (1/EarthRadius) or (1/6371010.0)      height *= 6371010.0;      scratchCartographic.height = height;      // Is it along a quad border - if so check if already exists and use that index      if (        indexOfEpsilon(quadBorderLongitudes, longitude) !== -1 ||        indexOfEpsilon(quadBorderLatitudes, latitude) !== -1      ) {        const index = indexOfEpsilon(          quadBorderPoints,          scratchCartographic,          Cartographic        );        if (index === -1) {          quadBorderPoints.push(Cartographic.clone(scratchCartographic));          quadBorderIndices.push(pointOffset);        } else {          indicesMapping[i] = quadBorderIndices[index];          continue;        }      }      indicesMapping[i] = pointOffset;      if (Math.abs(longitude - geographicWest) < halfStepX) {        westBorder.push({          index: pointOffset,          cartographic: Cartographic.clone(scratchCartographic),        });      } else if (Math.abs(longitude - geographicEast) < halfStepX) {        eastBorder.push({          index: pointOffset,          cartographic: Cartographic.clone(scratchCartographic),        });      } else if (Math.abs(latitude - geographicSouth) < halfStepY) {        southBorder.push({          index: pointOffset,          cartographic: Cartographic.clone(scratchCartographic),        });      } else if (Math.abs(latitude - geographicNorth) < halfStepY) {        northBorder.push({          index: pointOffset,          cartographic: Cartographic.clone(scratchCartographic),        });      }      minHeight = Math.min(height, minHeight);      maxHeight = Math.max(height, maxHeight);      heights[pointOffset] = height;      const pos = ellipsoid.cartographicToCartesian(scratchCartographic);      positions[pointOffset] = pos;      if (includeWebMercatorT) {        webMercatorTs[pointOffset] =          (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) -            southMercatorY) *          oneOverMercatorHeight;      }      if (includeGeodeticSurfaceNormals) {        const normal = ellipsoid.geodeticSurfaceNormal(pos);        geodeticSurfaceNormals[pointOffset] = normal;      }      Matrix4.multiplyByPoint(toENU, pos, scratchCartesian);      Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);      Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);      let u = (longitude - geographicWest) / (geographicEast - geographicWest);      u = CesiumMath.clamp(u, 0.0, 1.0);      let v =        (latitude - geographicSouth) / (geographicNorth - geographicSouth);      v = CesiumMath.clamp(v, 0.0, 1.0);      uvs[pointOffset] = new Cartesian2(u, v);      ++pointOffset;    }    const facesElementCount = numFaces * 3;    for (let j = 0; j < facesElementCount; ++j, ++indicesOffset) {      indices[indicesOffset] = indicesMapping[dv.getUint16(offset, true)];      offset += sizeOfUint16;    }    if (quadSize !== offset - startQuad) {      throw new RuntimeError("Invalid terrain tile.");    }  }  positions.length = pointOffset;  uvs.length = pointOffset;  heights.length = pointOffset;  if (includeWebMercatorT) {    webMercatorTs.length = pointOffset;  }  if (includeGeodeticSurfaceNormals) {    geodeticSurfaceNormals.length = pointOffset;  }  const vertexCountWithoutSkirts = pointOffset;  const indexCountWithoutSkirts = indicesOffset;  // Add skirt points  const skirtOptions = {    hMin: minHeight,    lastBorderPoint: undefined,    skirtHeight: skirtHeight,    toENU: toENU,    ellipsoid: ellipsoid,    minimum: minimum,    maximum: maximum,  };  // Sort counter clockwise from NW corner  // Corner points are in the east/west arrays  westBorder.sort(function (a, b) {    return b.cartographic.latitude - a.cartographic.latitude;  });  southBorder.sort(function (a, b) {    return a.cartographic.longitude - b.cartographic.longitude;  });  eastBorder.sort(function (a, b) {    return a.cartographic.latitude - b.cartographic.latitude;  });  northBorder.sort(function (a, b) {    return b.cartographic.longitude - a.cartographic.longitude;  });  const percentage = 0.00001;  addSkirt(    positions,    heights,    uvs,    webMercatorTs,    geodeticSurfaceNormals,    indices,    skirtOptions,    westBorder,    -percentage * rectangleWidth,    true,    -percentage * rectangleHeight  );  addSkirt(    positions,    heights,    uvs,    webMercatorTs,    geodeticSurfaceNormals,    indices,    skirtOptions,    southBorder,    -percentage * rectangleHeight,    false  );  addSkirt(    positions,    heights,    uvs,    webMercatorTs,    geodeticSurfaceNormals,    indices,    skirtOptions,    eastBorder,    percentage * rectangleWidth,    true,    percentage * rectangleHeight  );  addSkirt(    positions,    heights,    uvs,    webMercatorTs,    geodeticSurfaceNormals,    indices,    skirtOptions,    northBorder,    percentage * rectangleHeight,    false  );  // Since the corner between the north and west sides is in the west array, generate the last  //  two triangles between the last north vertex and the first west vertex  if (westBorder.length > 0 && northBorder.length > 0) {    const firstBorderIndex = westBorder[0].index;    const firstSkirtIndex = vertexCountWithoutSkirts;    const lastBorderIndex = northBorder[northBorder.length - 1].index;    const lastSkirtIndex = positions.length - 1;    indices.push(      lastBorderIndex,      lastSkirtIndex,      firstSkirtIndex,      firstSkirtIndex,      firstBorderIndex,      lastBorderIndex    );  }  size = positions.length; // Get new size with skirt vertices  const boundingSphere3D = BoundingSphere.fromPoints(positions);  let orientedBoundingBox;  if (defined(rectangle)) {    orientedBoundingBox = OrientedBoundingBox.fromRectangle(      rectangle,      minHeight,      maxHeight,      ellipsoid    );  }  const occluder = new EllipsoidalOccluder(ellipsoid);  const occludeePointInScaledSpace = occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(    relativeToCenter,    positions,    minHeight  );  const aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);  const encoding = new TerrainEncoding(    relativeToCenter,    aaBox,    skirtOptions.hMin,    maxHeight,    fromENU,    false,    includeWebMercatorT,    includeGeodeticSurfaceNormals,    exaggeration,    exaggerationRelativeHeight  );  const vertices = new Float32Array(size * encoding.stride);  let bufferIndex = 0;  for (let k = 0; k < size; ++k) {    bufferIndex = encoding.encode(      vertices,      bufferIndex,      positions[k],      uvs[k],      heights[k],      undefined,      webMercatorTs[k],      geodeticSurfaceNormals[k]    );  }  const westIndicesSouthToNorth = westBorder    .map(function (vertex) {      return vertex.index;    })    .reverse();  const southIndicesEastToWest = southBorder    .map(function (vertex) {      return vertex.index;    })    .reverse();  const eastIndicesNorthToSouth = eastBorder    .map(function (vertex) {      return vertex.index;    })    .reverse();  const northIndicesWestToEast = northBorder    .map(function (vertex) {      return vertex.index;    })    .reverse();  southIndicesEastToWest.unshift(    eastIndicesNorthToSouth[eastIndicesNorthToSouth.length - 1]  );  southIndicesEastToWest.push(westIndicesSouthToNorth[0]);  northIndicesWestToEast.unshift(    westIndicesSouthToNorth[westIndicesSouthToNorth.length - 1]  );  northIndicesWestToEast.push(eastIndicesNorthToSouth[0]);  return {    vertices: vertices,    indices: new Uint16Array(indices),    maximumHeight: maxHeight,    minimumHeight: minHeight,    encoding: encoding,    boundingSphere3D: boundingSphere3D,    orientedBoundingBox: orientedBoundingBox,    occludeePointInScaledSpace: occludeePointInScaledSpace,    vertexCountWithoutSkirts: vertexCountWithoutSkirts,    indexCountWithoutSkirts: indexCountWithoutSkirts,    westIndicesSouthToNorth: westIndicesSouthToNorth,    southIndicesEastToWest: southIndicesEastToWest,    eastIndicesNorthToSouth: eastIndicesNorthToSouth,    northIndicesWestToEast: northIndicesWestToEast,  };}function addSkirt(  positions,  heights,  uvs,  webMercatorTs,  geodeticSurfaceNormals,  indices,  skirtOptions,  borderPoints,  fudgeFactor,  eastOrWest,  cornerFudge) {  const count = borderPoints.length;  for (let j = 0; j < count; ++j) {    const borderPoint = borderPoints[j];    const borderCartographic = borderPoint.cartographic;    const borderIndex = borderPoint.index;    const currentIndex = positions.length;    const longitude = borderCartographic.longitude;    let latitude = borderCartographic.latitude;    latitude = CesiumMath.clamp(      latitude,      -CesiumMath.PI_OVER_TWO,      CesiumMath.PI_OVER_TWO    ); // Don't go over the poles    const height = borderCartographic.height - skirtOptions.skirtHeight;    skirtOptions.hMin = Math.min(skirtOptions.hMin, height);    Cartographic.fromRadians(longitude, latitude, height, scratchCartographic);    // Adjust sides to angle out    if (eastOrWest) {      scratchCartographic.longitude += fudgeFactor;    }    // Adjust top or bottom to angle out    // Since corners are in the east/west arrays angle the first and last points as well    if (!eastOrWest) {      scratchCartographic.latitude += fudgeFactor;    } else if (j === count - 1) {      scratchCartographic.latitude += cornerFudge;    } else if (j === 0) {      scratchCartographic.latitude -= cornerFudge;    }    const pos = skirtOptions.ellipsoid.cartographicToCartesian(      scratchCartographic    );    positions.push(pos);    heights.push(height);    uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point    if (webMercatorTs.length > 0) {      webMercatorTs.push(webMercatorTs[borderIndex]);    }    if (geodeticSurfaceNormals.length > 0) {      geodeticSurfaceNormals.push(geodeticSurfaceNormals[borderIndex]);    }    Matrix4.multiplyByPoint(skirtOptions.toENU, pos, scratchCartesian);    const minimum = skirtOptions.minimum;    const maximum = skirtOptions.maximum;    Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);    Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);    const lastBorderPoint = skirtOptions.lastBorderPoint;    if (defined(lastBorderPoint)) {      const lastBorderIndex = lastBorderPoint.index;      indices.push(        lastBorderIndex,        currentIndex - 1,        currentIndex,        currentIndex,        borderIndex,        lastBorderIndex      );    }    skirtOptions.lastBorderPoint = borderPoint;  }}export default createTaskProcessorWorker(  createVerticesFromGoogleEarthEnterpriseBuffer);
 |