| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937 | import Cartesian2 from "../Core/Cartesian2.js";import Cartesian3 from "../Core/Cartesian3.js";import Cartographic from "../Core/Cartographic.js";import Check from "../Core/Check.js";import ComponentDatatype from "../Core/ComponentDatatype.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import EncodedCartesian3 from "../Core/EncodedCartesian3.js";import GeometryInstanceAttribute from "../Core/GeometryInstanceAttribute.js";import CesiumMath from "../Core/Math.js";import Matrix4 from "../Core/Matrix4.js";import Rectangle from "../Core/Rectangle.js";import Transforms from "../Core/Transforms.js";import ShaderSource from "../Renderer/ShaderSource.js";import PerInstanceColorAppearance from "../Scene/PerInstanceColorAppearance.js";import ShadowVolumeAppearanceFS from "../Shaders/ShadowVolumeAppearanceFS.js";/** * Creates shaders for a ClassificationPrimitive to use a given Appearance, as well as for picking. * * @param {Boolean} extentsCulling Discard fragments outside the instance's texture coordinate extents. * @param {Boolean} planarExtents If true, texture coordinates will be computed using planes instead of spherical coordinates. * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive via GroundPrimitive. * @private */function ShadowVolumeAppearance(extentsCulling, planarExtents, appearance) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.bool("extentsCulling", extentsCulling);  Check.typeOf.bool("planarExtents", planarExtents);  Check.typeOf.object("appearance", appearance);  //>>includeEnd('debug');  this._projectionExtentDefines = {    eastMostYhighDefine: "",    eastMostYlowDefine: "",    westMostYhighDefine: "",    westMostYlowDefine: "",  };  // Compute shader dependencies  const colorShaderDependencies = new ShaderDependencies();  colorShaderDependencies.requiresTextureCoordinates = extentsCulling;  colorShaderDependencies.requiresEC = !appearance.flat;  const pickShaderDependencies = new ShaderDependencies();  pickShaderDependencies.requiresTextureCoordinates = extentsCulling;  if (appearance instanceof PerInstanceColorAppearance) {    // PerInstanceColorAppearance doesn't have material.shaderSource, instead it has its own vertex and fragment shaders    colorShaderDependencies.requiresNormalEC = !appearance.flat;  } else {    // Scan material source for what hookups are needed. Assume czm_materialInput materialInput.    const materialShaderSource = `${appearance.material.shaderSource}\n${appearance.fragmentShaderSource}`;    colorShaderDependencies.normalEC =      materialShaderSource.indexOf("materialInput.normalEC") !== -1 ||      materialShaderSource.indexOf("czm_getDefaultMaterial") !== -1;    colorShaderDependencies.positionToEyeEC =      materialShaderSource.indexOf("materialInput.positionToEyeEC") !== -1;    colorShaderDependencies.tangentToEyeMatrix =      materialShaderSource.indexOf("materialInput.tangentToEyeMatrix") !== -1;    colorShaderDependencies.st =      materialShaderSource.indexOf("materialInput.st") !== -1;  }  this._colorShaderDependencies = colorShaderDependencies;  this._pickShaderDependencies = pickShaderDependencies;  this._appearance = appearance;  this._extentsCulling = extentsCulling;  this._planarExtents = planarExtents;}/** * Create the fragment shader for a ClassificationPrimitive's color pass when rendering for color. * * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. * @returns {ShaderSource} Shader source for the fragment shader. */ShadowVolumeAppearance.prototype.createFragmentShader = function (  columbusView2D) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.bool("columbusView2D", columbusView2D);  //>>includeEnd('debug');  const appearance = this._appearance;  const dependencies = this._colorShaderDependencies;  const defines = [];  if (!columbusView2D && !this._planarExtents) {    defines.push("SPHERICAL");  }  if (dependencies.requiresEC) {    defines.push("REQUIRES_EC");  }  if (dependencies.requiresWC) {    defines.push("REQUIRES_WC");  }  if (dependencies.requiresTextureCoordinates) {    defines.push("TEXTURE_COORDINATES");  }  if (this._extentsCulling) {    defines.push("CULL_FRAGMENTS");  }  if (dependencies.requiresNormalEC) {    defines.push("NORMAL_EC");  }  if (appearance instanceof PerInstanceColorAppearance) {    defines.push("PER_INSTANCE_COLOR");  }  // Material inputs. Use of parameters in the material is different  // from requirement of the parameters in the overall shader, for example,  // texture coordinates may be used for fragment culling but not for the material itself.  if (dependencies.normalEC) {    defines.push("USES_NORMAL_EC");  }  if (dependencies.positionToEyeEC) {    defines.push("USES_POSITION_TO_EYE_EC");  }  if (dependencies.tangentToEyeMatrix) {    defines.push("USES_TANGENT_TO_EYE");  }  if (dependencies.st) {    defines.push("USES_ST");  }  if (appearance.flat) {    defines.push("FLAT");  }  let materialSource = "";  if (!(appearance instanceof PerInstanceColorAppearance)) {    materialSource = appearance.material.shaderSource;  }  return new ShaderSource({    defines: defines,    sources: [materialSource, ShadowVolumeAppearanceFS],  });};ShadowVolumeAppearance.prototype.createPickFragmentShader = function (  columbusView2D) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.bool("columbusView2D", columbusView2D);  //>>includeEnd('debug');  const dependencies = this._pickShaderDependencies;  const defines = ["PICK"];  if (!columbusView2D && !this._planarExtents) {    defines.push("SPHERICAL");  }  if (dependencies.requiresEC) {    defines.push("REQUIRES_EC");  }  if (dependencies.requiresWC) {    defines.push("REQUIRES_WC");  }  if (dependencies.requiresTextureCoordinates) {    defines.push("TEXTURE_COORDINATES");  }  if (this._extentsCulling) {    defines.push("CULL_FRAGMENTS");  }  return new ShaderSource({    defines: defines,    sources: [ShadowVolumeAppearanceFS],    pickColorQualifier: "varying",  });};/** * Create the vertex shader for a ClassificationPrimitive's color pass on the final of 3 shadow volume passes * * @param {String[]} defines External defines to pass to the vertex shader. * @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position. * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. * @param {MapProjection} mapProjection Current scene's map projection. * @returns {String} Shader source for the vertex shader. */ShadowVolumeAppearance.prototype.createVertexShader = function (  defines,  vertexShaderSource,  columbusView2D,  mapProjection) {  //>>includeStart('debug', pragmas.debug);  Check.defined("defines", defines);  Check.typeOf.string("vertexShaderSource", vertexShaderSource);  Check.typeOf.bool("columbusView2D", columbusView2D);  Check.defined("mapProjection", mapProjection);  //>>includeEnd('debug');  return createShadowVolumeAppearanceVS(    this._colorShaderDependencies,    this._planarExtents,    columbusView2D,    defines,    vertexShaderSource,    this._appearance,    mapProjection,    this._projectionExtentDefines  );};/** * Create the vertex shader for a ClassificationPrimitive's pick pass on the final of 3 shadow volume passes * * @param {String[]} defines External defines to pass to the vertex shader. * @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position and picking. * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. * @param {MapProjection} mapProjection Current scene's map projection. * @returns {String} Shader source for the vertex shader. */ShadowVolumeAppearance.prototype.createPickVertexShader = function (  defines,  vertexShaderSource,  columbusView2D,  mapProjection) {  //>>includeStart('debug', pragmas.debug);  Check.defined("defines", defines);  Check.typeOf.string("vertexShaderSource", vertexShaderSource);  Check.typeOf.bool("columbusView2D", columbusView2D);  Check.defined("mapProjection", mapProjection);  //>>includeEnd('debug');  return createShadowVolumeAppearanceVS(    this._pickShaderDependencies,    this._planarExtents,    columbusView2D,    defines,    vertexShaderSource,    undefined,    mapProjection,    this._projectionExtentDefines  );};const longitudeExtentsCartesianScratch = new Cartesian3();const longitudeExtentsCartographicScratch = new Cartographic();const longitudeExtentsEncodeScratch = {  high: 0.0,  low: 0.0,};function createShadowVolumeAppearanceVS(  shaderDependencies,  planarExtents,  columbusView2D,  defines,  vertexShaderSource,  appearance,  mapProjection,  projectionExtentDefines) {  const allDefines = defines.slice();  if (projectionExtentDefines.eastMostYhighDefine === "") {    const eastMostCartographic = longitudeExtentsCartographicScratch;    eastMostCartographic.longitude = CesiumMath.PI;    eastMostCartographic.latitude = 0.0;    eastMostCartographic.height = 0.0;    const eastMostCartesian = mapProjection.project(      eastMostCartographic,      longitudeExtentsCartesianScratch    );    let encoded = EncodedCartesian3.encode(      eastMostCartesian.x,      longitudeExtentsEncodeScratch    );    projectionExtentDefines.eastMostYhighDefine = `EAST_MOST_X_HIGH ${encoded.high.toFixed(      `${encoded.high}`.length + 1    )}`;    projectionExtentDefines.eastMostYlowDefine = `EAST_MOST_X_LOW ${encoded.low.toFixed(      `${encoded.low}`.length + 1    )}`;    const westMostCartographic = longitudeExtentsCartographicScratch;    westMostCartographic.longitude = -CesiumMath.PI;    westMostCartographic.latitude = 0.0;    westMostCartographic.height = 0.0;    const westMostCartesian = mapProjection.project(      westMostCartographic,      longitudeExtentsCartesianScratch    );    encoded = EncodedCartesian3.encode(      westMostCartesian.x,      longitudeExtentsEncodeScratch    );    projectionExtentDefines.westMostYhighDefine = `WEST_MOST_X_HIGH ${encoded.high.toFixed(      `${encoded.high}`.length + 1    )}`;    projectionExtentDefines.westMostYlowDefine = `WEST_MOST_X_LOW ${encoded.low.toFixed(      `${encoded.low}`.length + 1    )}`;  }  if (columbusView2D) {    allDefines.push(projectionExtentDefines.eastMostYhighDefine);    allDefines.push(projectionExtentDefines.eastMostYlowDefine);    allDefines.push(projectionExtentDefines.westMostYhighDefine);    allDefines.push(projectionExtentDefines.westMostYlowDefine);  }  if (defined(appearance) && appearance instanceof PerInstanceColorAppearance) {    allDefines.push("PER_INSTANCE_COLOR");  }  if (shaderDependencies.requiresTextureCoordinates) {    allDefines.push("TEXTURE_COORDINATES");    if (!(planarExtents || columbusView2D)) {      allDefines.push("SPHERICAL");    }    if (columbusView2D) {      allDefines.push("COLUMBUS_VIEW_2D");    }  }  return new ShaderSource({    defines: allDefines,    sources: [vertexShaderSource],  });}/** * Tracks shader dependencies. * @private */function ShaderDependencies() {  this._requiresEC = false;  this._requiresWC = false; // depends on eye coordinates, needed for material and for phong  this._requiresNormalEC = false; // depends on eye coordinates, needed for material  this._requiresTextureCoordinates = false; // depends on world coordinates, needed for material and for culling  this._usesNormalEC = false;  this._usesPositionToEyeEC = false;  this._usesTangentToEyeMat = false;  this._usesSt = false;}Object.defineProperties(ShaderDependencies.prototype, {  // Set when assessing final shading (flat vs. phong) and culling using computed texture coordinates  requiresEC: {    get: function () {      return this._requiresEC;    },    set: function (value) {      this._requiresEC = value || this._requiresEC;    },  },  requiresWC: {    get: function () {      return this._requiresWC;    },    set: function (value) {      this._requiresWC = value || this._requiresWC;      this.requiresEC = this._requiresWC;    },  },  requiresNormalEC: {    get: function () {      return this._requiresNormalEC;    },    set: function (value) {      this._requiresNormalEC = value || this._requiresNormalEC;      this.requiresEC = this._requiresNormalEC;    },  },  requiresTextureCoordinates: {    get: function () {      return this._requiresTextureCoordinates;    },    set: function (value) {      this._requiresTextureCoordinates =        value || this._requiresTextureCoordinates;      this.requiresWC = this._requiresTextureCoordinates;    },  },  // Get/Set when assessing material hookups  normalEC: {    set: function (value) {      this.requiresNormalEC = value;      this._usesNormalEC = value;    },    get: function () {      return this._usesNormalEC;    },  },  tangentToEyeMatrix: {    set: function (value) {      this.requiresWC = value;      this.requiresNormalEC = value;      this._usesTangentToEyeMat = value;    },    get: function () {      return this._usesTangentToEyeMat;    },  },  positionToEyeEC: {    set: function (value) {      this.requiresEC = value;      this._usesPositionToEyeEC = value;    },    get: function () {      return this._usesPositionToEyeEC;    },  },  st: {    set: function (value) {      this.requiresTextureCoordinates = value;      this._usesSt = value;    },    get: function () {      return this._usesSt;    },  },});function pointLineDistance(point1, point2, point) {  return (    Math.abs(      (point2.y - point1.y) * point.x -        (point2.x - point1.x) * point.y +        point2.x * point1.y -        point2.y * point1.x    ) / Cartesian2.distance(point2, point1)  );}const points2DScratch = [  new Cartesian2(),  new Cartesian2(),  new Cartesian2(),  new Cartesian2(),];// textureCoordinateRotationPoints form 2 lines in the computed UV space that remap to desired texture coordinates.// This allows simulation of baked texture coordinates for EllipseGeometry, RectangleGeometry, and PolygonGeometry.function addTextureCoordinateRotationAttributes(  attributes,  textureCoordinateRotationPoints) {  const points2D = points2DScratch;  const minXYCorner = Cartesian2.unpack(    textureCoordinateRotationPoints,    0,    points2D[0]  );  const maxYCorner = Cartesian2.unpack(    textureCoordinateRotationPoints,    2,    points2D[1]  );  const maxXCorner = Cartesian2.unpack(    textureCoordinateRotationPoints,    4,    points2D[2]  );  attributes.uMaxVmax = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 4,    normalize: false,    value: [maxYCorner.x, maxYCorner.y, maxXCorner.x, maxXCorner.y],  });  const inverseExtentX =    1.0 / pointLineDistance(minXYCorner, maxYCorner, maxXCorner);  const inverseExtentY =    1.0 / pointLineDistance(minXYCorner, maxXCorner, maxYCorner);  attributes.uvMinAndExtents = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 4,    normalize: false,    value: [minXYCorner.x, minXYCorner.y, inverseExtentX, inverseExtentY],  });}const cartographicScratch = new Cartographic();const cornerScratch = new Cartesian3();const northWestScratch = new Cartesian3();const southEastScratch = new Cartesian3();const highLowScratch = { high: 0.0, low: 0.0 };function add2DTextureCoordinateAttributes(rectangle, projection, attributes) {  // Compute corner positions in double precision  const carto = cartographicScratch;  carto.height = 0.0;  carto.longitude = rectangle.west;  carto.latitude = rectangle.south;  const southWestCorner = projection.project(carto, cornerScratch);  carto.latitude = rectangle.north;  const northWest = projection.project(carto, northWestScratch);  carto.longitude = rectangle.east;  carto.latitude = rectangle.south;  const southEast = projection.project(carto, southEastScratch);  // Since these positions are all in the 2D plane, there's a lot of zeros  // and a lot of repetition. So we only need to encode 4 values.  // Encode:  // x: x value for southWestCorner  // y: y value for southWestCorner  // z: y value for northWest  // w: x value for southEast  const valuesHigh = [0, 0, 0, 0];  const valuesLow = [0, 0, 0, 0];  let encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch);  valuesHigh[0] = encoded.high;  valuesLow[0] = encoded.low;  encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch);  valuesHigh[1] = encoded.high;  valuesLow[1] = encoded.low;  encoded = EncodedCartesian3.encode(northWest.y, highLowScratch);  valuesHigh[2] = encoded.high;  valuesLow[2] = encoded.low;  encoded = EncodedCartesian3.encode(southEast.x, highLowScratch);  valuesHigh[3] = encoded.high;  valuesLow[3] = encoded.low;  attributes.planes2D_HIGH = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 4,    normalize: false,    value: valuesHigh,  });  attributes.planes2D_LOW = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 4,    normalize: false,    value: valuesLow,  });}const enuMatrixScratch = new Matrix4();const inverseEnuScratch = new Matrix4();const rectanglePointCartesianScratch = new Cartesian3();const rectangleCenterScratch = new Cartographic();const pointsCartographicScratch = [  new Cartographic(),  new Cartographic(),  new Cartographic(),  new Cartographic(),  new Cartographic(),  new Cartographic(),  new Cartographic(),  new Cartographic(),];/** * When computing planes to bound the rectangle, * need to factor in "bulge" and other distortion. * Flatten the ellipsoid-centered corners and edge-centers of the rectangle * into the plane of the local ENU system, compute bounds in 2D, and * project back to ellipsoid-centered. * * @private */function computeRectangleBounds(  rectangle,  ellipsoid,  height,  southWestCornerResult,  eastVectorResult,  northVectorResult) {  // Compute center of rectangle  const centerCartographic = Rectangle.center(    rectangle,    rectangleCenterScratch  );  centerCartographic.height = height;  const centerCartesian = Cartographic.toCartesian(    centerCartographic,    ellipsoid,    rectanglePointCartesianScratch  );  const enuMatrix = Transforms.eastNorthUpToFixedFrame(    centerCartesian,    ellipsoid,    enuMatrixScratch  );  const inverseEnu = Matrix4.inverse(enuMatrix, inverseEnuScratch);  const west = rectangle.west;  const east = rectangle.east;  const north = rectangle.north;  const south = rectangle.south;  const cartographics = pointsCartographicScratch;  cartographics[0].latitude = south;  cartographics[0].longitude = west;  cartographics[1].latitude = north;  cartographics[1].longitude = west;  cartographics[2].latitude = north;  cartographics[2].longitude = east;  cartographics[3].latitude = south;  cartographics[3].longitude = east;  const longitudeCenter = (west + east) * 0.5;  const latitudeCenter = (north + south) * 0.5;  cartographics[4].latitude = south;  cartographics[4].longitude = longitudeCenter;  cartographics[5].latitude = north;  cartographics[5].longitude = longitudeCenter;  cartographics[6].latitude = latitudeCenter;  cartographics[6].longitude = west;  cartographics[7].latitude = latitudeCenter;  cartographics[7].longitude = east;  let minX = Number.POSITIVE_INFINITY;  let maxX = Number.NEGATIVE_INFINITY;  let minY = Number.POSITIVE_INFINITY;  let maxY = Number.NEGATIVE_INFINITY;  for (let i = 0; i < 8; i++) {    cartographics[i].height = height;    const pointCartesian = Cartographic.toCartesian(      cartographics[i],      ellipsoid,      rectanglePointCartesianScratch    );    Matrix4.multiplyByPoint(inverseEnu, pointCartesian, pointCartesian);    pointCartesian.z = 0.0; // flatten into XY plane of ENU coordinate system    minX = Math.min(minX, pointCartesian.x);    maxX = Math.max(maxX, pointCartesian.x);    minY = Math.min(minY, pointCartesian.y);    maxY = Math.max(maxY, pointCartesian.y);  }  const southWestCorner = southWestCornerResult;  southWestCorner.x = minX;  southWestCorner.y = minY;  southWestCorner.z = 0.0;  Matrix4.multiplyByPoint(enuMatrix, southWestCorner, southWestCorner);  const southEastCorner = eastVectorResult;  southEastCorner.x = maxX;  southEastCorner.y = minY;  southEastCorner.z = 0.0;  Matrix4.multiplyByPoint(enuMatrix, southEastCorner, southEastCorner);  // make eastward vector  Cartesian3.subtract(southEastCorner, southWestCorner, eastVectorResult);  const northWestCorner = northVectorResult;  northWestCorner.x = minX;  northWestCorner.y = maxY;  northWestCorner.z = 0.0;  Matrix4.multiplyByPoint(enuMatrix, northWestCorner, northWestCorner);  // make eastward vector  Cartesian3.subtract(northWestCorner, southWestCorner, northVectorResult);}const eastwardScratch = new Cartesian3();const northwardScratch = new Cartesian3();const encodeScratch = new EncodedCartesian3();/** * Gets an attributes object containing: * - 3 high-precision points as 6 GeometryInstanceAttributes. These points are used to compute eye-space planes. * - 1 texture coordinate rotation GeometryInstanceAttributes * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View. *   These points are used to compute eye-space planes like above. * * Used to compute texture coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances. * * @see ShadowVolumeAppearance * @private * * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound * @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @param {Number} [height=0] The maximum height for the shadow volume. * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. */ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function (  boundingRectangle,  textureCoordinateRotationPoints,  ellipsoid,  projection,  height) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("boundingRectangle", boundingRectangle);  Check.defined(    "textureCoordinateRotationPoints",    textureCoordinateRotationPoints  );  Check.typeOf.object("ellipsoid", ellipsoid);  Check.typeOf.object("projection", projection);  //>>includeEnd('debug');  const corner = cornerScratch;  const eastward = eastwardScratch;  const northward = northwardScratch;  computeRectangleBounds(    boundingRectangle,    ellipsoid,    defaultValue(height, 0.0),    corner,    eastward,    northward  );  const attributes = {};  addTextureCoordinateRotationAttributes(    attributes,    textureCoordinateRotationPoints  );  const encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch);  attributes.southWest_HIGH = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 3,    normalize: false,    value: Cartesian3.pack(encoded.high, [0, 0, 0]),  });  attributes.southWest_LOW = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 3,    normalize: false,    value: Cartesian3.pack(encoded.low, [0, 0, 0]),  });  attributes.eastward = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 3,    normalize: false,    value: Cartesian3.pack(eastward, [0, 0, 0]),  });  attributes.northward = new GeometryInstanceAttribute({    componentDatatype: ComponentDatatype.FLOAT,    componentsPerAttribute: 3,    normalize: false,    value: Cartesian3.pack(northward, [0, 0, 0]),  });  add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);  return attributes;};const spherePointScratch = new Cartesian3();function latLongToSpherical(latitude, longitude, ellipsoid, result) {  const cartographic = cartographicScratch;  cartographic.latitude = latitude;  cartographic.longitude = longitude;  cartographic.height = 0.0;  const spherePoint = Cartographic.toCartesian(    cartographic,    ellipsoid,    spherePointScratch  );  // Project into plane with vertical for latitude  const magXY = Math.sqrt(    spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y  );  // Use fastApproximateAtan2 for alignment with shader  const sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);  const sphereLongitude = CesiumMath.fastApproximateAtan2(    spherePoint.x,    spherePoint.y  );  result.x = sphereLatitude;  result.y = sphereLongitude;  return result;}const sphericalScratch = new Cartesian2();/** * Gets an attributes object containing: * - the southwest corner of a rectangular area in spherical coordinates, as well as the inverse of the latitude/longitude range. *   These are computed using the same atan2 approximation used in the shader. * - 1 texture coordinate rotation GeometryInstanceAttributes * - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View. *   These points are used to compute eye-space planes like above. * * Used when computing texture coordinates for large-area ClassificationPrimitives with materials or * multiple non-overlapping instances. * @see ShadowVolumeAppearance * @private * * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound * @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. */ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function (  boundingRectangle,  textureCoordinateRotationPoints,  ellipsoid,  projection) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("boundingRectangle", boundingRectangle);  Check.defined(    "textureCoordinateRotationPoints",    textureCoordinateRotationPoints  );  Check.typeOf.object("ellipsoid", ellipsoid);  Check.typeOf.object("projection", projection);  //>>includeEnd('debug');  // rectangle cartographic coords !== spherical because it's on an ellipsoid  const southWestExtents = latLongToSpherical(    boundingRectangle.south,    boundingRectangle.west,    ellipsoid,    sphericalScratch  );  let south = southWestExtents.x;  let west = southWestExtents.y;  const northEastExtents = latLongToSpherical(    boundingRectangle.north,    boundingRectangle.east,    ellipsoid,    sphericalScratch  );  let north = northEastExtents.x;  let east = northEastExtents.y;  // If the bounding rectangle crosses the IDL, rotate the spherical extents so the cross no longer happens.  // This rotation must happen in the shader too.  let rotationRadians = 0.0;  if (west > east) {    rotationRadians = CesiumMath.PI - west;    west = -CesiumMath.PI;    east += rotationRadians;  }  // Slightly pad extents to avoid floating point error when fragment culling at edges.  south -= CesiumMath.EPSILON5;  west -= CesiumMath.EPSILON5;  north += CesiumMath.EPSILON5;  east += CesiumMath.EPSILON5;  const longitudeRangeInverse = 1.0 / (east - west);  const latitudeRangeInverse = 1.0 / (north - south);  const attributes = {    sphericalExtents: new GeometryInstanceAttribute({      componentDatatype: ComponentDatatype.FLOAT,      componentsPerAttribute: 4,      normalize: false,      value: [south, west, latitudeRangeInverse, longitudeRangeInverse],    }),    longitudeRotation: new GeometryInstanceAttribute({      componentDatatype: ComponentDatatype.FLOAT,      componentsPerAttribute: 1,      normalize: false,      value: [rotationRadians],    }),  };  addTextureCoordinateRotationAttributes(    attributes,    textureCoordinateRotationPoints  );  add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);  return attributes;};ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function (  attributes) {  return (    defined(attributes.southWest_HIGH) &&    defined(attributes.southWest_LOW) &&    defined(attributes.northward) &&    defined(attributes.eastward) &&    defined(attributes.planes2D_HIGH) &&    defined(attributes.planes2D_LOW) &&    defined(attributes.uMaxVmax) &&    defined(attributes.uvMinAndExtents)  );};ShadowVolumeAppearance.hasAttributesForSphericalExtents = function (  attributes) {  return (    defined(attributes.sphericalExtents) &&    defined(attributes.longitudeRotation) &&    defined(attributes.planes2D_HIGH) &&    defined(attributes.planes2D_LOW) &&    defined(attributes.uMaxVmax) &&    defined(attributes.uvMinAndExtents)  );};function shouldUseSpherical(rectangle) {  return (    Math.max(rectangle.width, rectangle.height) >    ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS  );}/** * Computes whether the given rectangle is wide enough that texture coordinates * over its area should be computed using spherical extents instead of distance to planes. * * @param {Rectangle} rectangle A rectangle * @private */ShadowVolumeAppearance.shouldUseSphericalCoordinates = function (rectangle) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("rectangle", rectangle);  //>>includeEnd('debug');  return shouldUseSpherical(rectangle);};/** * Texture coordinates for ground primitives are computed either using spherical coordinates for large areas or * using distance from planes for small areas. * * @type {Number} * @constant * @private */ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0);export default ShadowVolumeAppearance;
 |