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 defineProperties from '../Core/defineProperties.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. * @param {Boolean} useFloatBatchTable Whether or not the ShadowVolumeAppearance should use floating point batch table values. * @private */ function ShadowVolumeAppearance(extentsCulling, planarExtents, appearance, useFloatBatchTable) { //>>includeStart('debug', pragmas.debug); Check.typeOf.bool('extentsCulling', extentsCulling); Check.typeOf.bool('planarExtents', planarExtents); Check.typeOf.object('appearance', appearance); Check.typeOf.bool('useFloatBatchTable', useFloatBatchTable); //>>includeEnd('debug'); this._projectionExtentDefines = { eastMostYhighDefine : '', eastMostYlowDefine : '', westMostYhighDefine : '', westMostYlowDefine : '' }; this._useFloatBatchTable = useFloatBatchTable; // Compute shader dependencies var colorShaderDependencies = new ShaderDependencies(); colorShaderDependencies.requiresTextureCoordinates = extentsCulling; colorShaderDependencies.requiresEC = !appearance.flat; var 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. var 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'); var appearance = this._appearance; var dependencies = this._colorShaderDependencies; var 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'); } var 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'); var dependencies = this._pickShaderDependencies; var 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._useFloatBatchTable, 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._useFloatBatchTable, this._projectionExtentDefines); }; var longitudeExtentsCartesianScratch = new Cartesian3(); var longitudeExtentsCartographicScratch = new Cartographic(); var longitudeExtentsEncodeScratch = { high : 0.0, low : 0.0 }; function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance, mapProjection, useFloatBatchTable, projectionExtentDefines) { var allDefines = defines.slice(); if (projectionExtentDefines.eastMostYhighDefine === '') { var eastMostCartographic = longitudeExtentsCartographicScratch; eastMostCartographic.longitude = CesiumMath.PI; eastMostCartographic.latitude = 0.0; eastMostCartographic.height = 0.0; var eastMostCartesian = mapProjection.project(eastMostCartographic, longitudeExtentsCartesianScratch); var 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); var westMostCartographic = longitudeExtentsCartographicScratch; westMostCartographic.longitude = -CesiumMath.PI; westMostCartographic.latitude = 0.0; westMostCartographic.height = 0.0; var 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'); } } if (!useFloatBatchTable) { allDefines.push('UINT8_PACKING'); } 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; } 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); } var 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) { var points2D = points2DScratch; var minXYCorner = Cartesian2.unpack(textureCoordinateRotationPoints, 0, points2D[0]); var maxYCorner = Cartesian2.unpack(textureCoordinateRotationPoints, 2, points2D[1]); var 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] }); var inverseExtentX = 1.0 / pointLineDistance(minXYCorner, maxYCorner, maxXCorner); var 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] }); } function encodeLowLessThan100k(value, valueName, attributes) { // Encode a value like 12,345.678 to 4 uint8 values: 12 34 56 78 var fract = Math.abs(value); var d12 = Math.floor(fract / 1000); fract -= d12 * 1000; // 345.678 var d34 = Math.floor(fract / 10); fract -= d34 * 10; // 5.678 var d56 = Math.floor(fract * 10); fract -= d56 * 0.1; // 0.078 var d78 = Math.floor(fract * 1000); if (value < 0) { d12 = 255 - d12; } attributes[valueName] = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute: 4, normalize: false, value : [d12, d34, d56, d78] }); } function encodeHighLessThan100Million(value, valueName, attributes) { // Encode a value like -12,345,678 to 4 uint8 values: sign+12 34 56 78 var fract = Math.abs(value); var d12 = Math.floor(fract / 1000000); fract -= d12 * 1000000; // 345678 var d34 = Math.floor(fract / 10000); fract -= d34 * 10000; // 5678 var d56 = Math.floor(fract / 100); fract -= d56 * 100; // 78 var d78 = Math.floor(fract); if (value < 0) { d12 = 255 - d12; } attributes[valueName] = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute: 4, normalize: false, value : [d12, d34, d56, d78] }); } function encodeLessThan1000k(value, valueName, attributes) { // Encode a value like -123456.78 to 4 uint8 values sign+12 34 56 78 var fract = Math.abs(value); var d12 = Math.floor(fract / 10000); fract -= d12 * 10000; // 3456.78 var d34 = Math.floor(fract / 100); fract -= d34 * 100; // 56.78 var d56 = Math.floor(fract); fract -= d56; // 0.78 var d78 = Math.floor(fract / 0.001); if (value < 0) { d12 = 255 - d12; } attributes[valueName] = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute: 4, normalize: false, value : [d12, d34, d56, d78] }); } var cartographicScratch = new Cartographic(); var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); var highLowScratch = {high : 0.0, low : 0.0}; function add2DTextureCoordinateAttributes(rectangle, projection, attributes, useFloatBatchTable) { // Compute corner positions in double precision var carto = cartographicScratch; carto.height = 0.0; carto.longitude = rectangle.west; carto.latitude = rectangle.south; var southWestCorner = projection.project(carto, cornerScratch); carto.latitude = rectangle.north; var northWest = projection.project(carto, northWestScratch); carto.longitude = rectangle.east; carto.latitude = rectangle.south; var 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 var encoded; if (!useFloatBatchTable) { encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch); encodeHighLessThan100Million(encoded.high, 'planes2D_HIGH_x', attributes); encodeLowLessThan100k(encoded.low, 'planes2D_LOW_x', attributes); encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); encodeHighLessThan100Million(encoded.high, 'planes2D_HIGH_y', attributes); encodeLowLessThan100k(encoded.low, 'planes2D_LOW_y', attributes); encoded = EncodedCartesian3.encode(northWest.y, highLowScratch); encodeHighLessThan100Million(encoded.high, 'planes2D_HIGH_z', attributes); encodeLowLessThan100k(encoded.low, 'planes2D_LOW_z', attributes); encoded = EncodedCartesian3.encode(southEast.x, highLowScratch); encodeHighLessThan100Million(encoded.high, 'planes2D_HIGH_w', attributes); encodeLowLessThan100k(encoded.low, 'planes2D_LOW_w', attributes); return; } var valuesHigh = [0, 0, 0, 0]; var valuesLow = [0, 0, 0, 0]; 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 }); } var enuMatrixScratch = new Matrix4(); var inverseEnuScratch = new Matrix4(); var rectanglePointCartesianScratch = new Cartesian3(); var rectangleCenterScratch = new Cartographic(); var 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. */ function computeRectangleBounds(rectangle, ellipsoid, height, southWestCornerResult, eastVectorResult, northVectorResult) { // Compute center of rectangle var centerCartographic = Rectangle.center(rectangle, rectangleCenterScratch); centerCartographic.height = height; var centerCartesian = Cartographic.toCartesian(centerCartographic, ellipsoid, rectanglePointCartesianScratch); var enuMatrix = Transforms.eastNorthUpToFixedFrame(centerCartesian, ellipsoid, enuMatrixScratch); var inverseEnu = Matrix4.inverse(enuMatrix, inverseEnuScratch); var west = rectangle.west; var east = rectangle.east; var north = rectangle.north; var south = rectangle.south; var 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; var longitudeCenter = (west + east) * 0.5; var 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; var minX = Number.POSITIVE_INFINITY; var maxX = Number.NEGATIVE_INFINITY; var minY = Number.POSITIVE_INFINITY; var maxY = Number.NEGATIVE_INFINITY; for (var i = 0; i < 8; i++) { cartographics[i].height = height; var 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); } var southWestCorner = southWestCornerResult; southWestCorner.x = minX; southWestCorner.y = minY; southWestCorner.z = 0.0; Matrix4.multiplyByPoint(enuMatrix, southWestCorner, southWestCorner); var 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); var 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); } var eastwardScratch = new Cartesian3(); var northwardScratch = new Cartesian3(); var 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 {Boolean} useFloatBatchTable Whether or not the ShadowVolumeAppearance should use floating point batch table values. * @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, useFloatBatchTable, height) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); Check.defined('textureCoordinateRotationPoints', textureCoordinateRotationPoints); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); Check.typeOf.bool('useFloatBatchTable', useFloatBatchTable); //>>includeEnd('debug'); var corner = cornerScratch; var eastward = eastwardScratch; var northward = northwardScratch; computeRectangleBounds(boundingRectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); var attributes = {}; addTextureCoordinateRotationAttributes(attributes, textureCoordinateRotationPoints); var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch); if (!useFloatBatchTable) { var high = encoded.high; encodeHighLessThan100Million(high.x, 'southWest_HIGH_x', attributes); encodeHighLessThan100Million(high.y, 'southWest_HIGH_y', attributes); encodeHighLessThan100Million(high.z, 'southWest_HIGH_z', attributes); var low = encoded.low; encodeLowLessThan100k(low.x, 'southWest_LOW_x', attributes); encodeLowLessThan100k(low.y, 'southWest_LOW_y', attributes); encodeLowLessThan100k(low.z, 'southWest_LOW_z', attributes); encodeLessThan1000k(eastward.x, 'eastward_x', attributes); encodeLessThan1000k(eastward.y, 'eastward_y', attributes); encodeLessThan1000k(eastward.z, 'eastward_z', attributes); encodeLessThan1000k(northward.x, 'northward_x', attributes); encodeLessThan1000k(northward.y, 'northward_y', attributes); encodeLessThan1000k(northward.z, 'northward_z', attributes); add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes, false); return attributes; } 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, true); return attributes; }; var spherePointScratch = new Cartesian3(); function latLongToSpherical(latitude, longitude, ellipsoid, result) { var cartographic = cartographicScratch; cartographic.latitude = latitude; cartographic.longitude = longitude; cartographic.height = 0.0; var spherePoint = Cartographic.toCartesian(cartographic, ellipsoid, spherePointScratch); // Project into plane with vertical for latitude var magXY = Math.sqrt(spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y); // Use fastApproximateAtan2 for alignment with shader var sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z); var sphereLongitude = CesiumMath.fastApproximateAtan2(spherePoint.x, spherePoint.y); result.x = sphereLatitude; result.y = sphereLongitude; return result; } var 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. * @param {Boolean} useFloatBatchTable Whether or not the ShadowVolumeAppearance should use floating point batch table values. * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. */ ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, projection, useFloatBatchTable) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); Check.defined('textureCoordinateRotationPoints', textureCoordinateRotationPoints); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); Check.typeOf.bool('useFloatBatchTable', useFloatBatchTable); //>>includeEnd('debug'); // rectangle cartographic coords !== spherical because it's on an ellipsoid var southWestExtents = latLongToSpherical(boundingRectangle.south, boundingRectangle.west, ellipsoid, sphericalScratch); var south = southWestExtents.x; var west = southWestExtents.y; var northEastExtents = latLongToSpherical(boundingRectangle.north, boundingRectangle.east, ellipsoid, sphericalScratch); var north = northEastExtents.x; var 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. var 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; var longitudeRangeInverse = 1.0 / (east - west); var latitudeRangeInverse = 1.0 / (north - south); var 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, useFloatBatchTable); return attributes; }; ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function(attributes) { var hasFloatAttributes = 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); var hasUint8Attributes = defined(attributes.southWest_HIGH_x) && defined(attributes.southWest_LOW_x) && defined(attributes.southWest_HIGH_y) && defined(attributes.southWest_LOW_y) && defined(attributes.southWest_HIGH_z) && defined(attributes.southWest_LOW_z) && defined(attributes.northward_x) && defined(attributes.eastward_x) && defined(attributes.northward_y) && defined(attributes.eastward_y) && defined(attributes.northward_z) && defined(attributes.eastward_z) && defined(attributes.planes2D_HIGH_x) && defined(attributes.planes2D_LOW_x) && defined(attributes.planes2D_HIGH_y) && defined(attributes.planes2D_LOW_y) && defined(attributes.planes2D_HIGH_z) && defined(attributes.planes2D_LOW_z) && defined(attributes.planes2D_HIGH_w) && defined(attributes.planes2D_LOW_w) && defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); return hasFloatAttributes || hasUint8Attributes; }; ShadowVolumeAppearance.hasAttributesForSphericalExtents = function(attributes) { var hasFloatAttributes = defined(attributes.sphericalExtents) && defined(attributes.longitudeRotation) && defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); var hasUint8Attributes = defined(attributes.sphericalExtents) && defined(attributes.longitudeRotation) && defined(attributes.planes2D_HIGH_x) && defined(attributes.planes2D_LOW_x) && defined(attributes.planes2D_HIGH_y) && defined(attributes.planes2D_LOW_y) && defined(attributes.planes2D_HIGH_z) && defined(attributes.planes2D_LOW_z) && defined(attributes.planes2D_HIGH_w) && defined(attributes.planes2D_LOW_w) && defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); return hasFloatAttributes || hasUint8Attributes; }; 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;