diff --git a/CHANGES.md b/CHANGES.md index dae9330e0831..ba9c8e17ff3f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ Change Log * Fixed labels not showing for individual entities in data sources when clustering is enabled. [#6087](https://github.com/AnalyticalGraphicsInc/cesium/issues/6087) * Fixed a crash for 3D Tiles that have zero volume. [#7945](https://github.com/AnalyticalGraphicsInc/cesium/pull/7945) * Fixed a bug where dynamic polylines did not use the given arcType. [#8191](https://github.com/AnalyticalGraphicsInc/cesium/issues/8191) +* Fixed an issue where polygons, corridors, rectangles, and ellipses on terrain would not render on some mobile devices. [#6739](https://github.com/AnalyticalGraphicsInc/cesium/issues/6739) * Fixed a bug where GlobeSurfaceTile would not render the tile until all layers completed loading causing globe to appear to hang. [#7974](https://github.com/AnalyticalGraphicsInc/cesium/issues/7974) ### 1.61 - 2019-09-03 diff --git a/Source/Renderer/ComputeCommand.js b/Source/Renderer/ComputeCommand.js index 0ed531cdd1bb..071fac2a5eaa 100644 --- a/Source/Renderer/ComputeCommand.js +++ b/Source/Renderer/ComputeCommand.js @@ -107,7 +107,7 @@ define([ /** * Executes the compute command. * - * @param {Context} computeEngine The context that processes the compute command. + * @param {ComputeEngine} computeEngine The context that processes the compute command. */ ComputeCommand.prototype.execute = function(computeEngine) { computeEngine.execute(this); diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 306d3cc5b60a..9dd5eba7c0ff 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -18,6 +18,7 @@ define([ '../Core/WebGLConstants', '../Shaders/ViewportQuadVS', './BufferUsage', + './checkFloatTexturePrecision', './ClearCommand', './ContextLimits', './CubeMap', @@ -51,6 +52,7 @@ define([ WebGLConstants, ViewportQuadVS, BufferUsage, + checkFloatTexturePrecision, ClearCommand, ContextLimits, CubeMap, @@ -445,6 +447,8 @@ define([ this.cache = {}; RenderState.apply(gl, rs, ps); + + this._floatTexSixPlaces = checkFloatTexturePrecision(this); } var defaultFramebufferMarker = {}; @@ -586,6 +590,18 @@ define([ } }, + /** + * Returns true if the context's floating point textures support 6 decimal places of precision. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/OES_texture_float/} + */ + floatTextureSixPlaces : { + get : function() { + return this._floatTexSixPlaces; + } + }, + /** * true if OES_texture_half_float is supported. This extension provides * access to floating point textures that, for example, can be attached to framebuffers for high dynamic range. diff --git a/Source/Renderer/checkFloatTexturePrecision.js b/Source/Renderer/checkFloatTexturePrecision.js new file mode 100644 index 000000000000..2e00e1100a7c --- /dev/null +++ b/Source/Renderer/checkFloatTexturePrecision.js @@ -0,0 +1,100 @@ +define([ + '../Core/PixelFormat', + '../Shaders/CheckFloatTexturePrecisionFS', + './ComputeCommand', + './ComputeEngine', + './Framebuffer', + './PixelDatatype', + './Texture' + ], function( + PixelFormat, + CheckFloatTexturePrecisionFS, + ComputeCommand, + ComputeEngine, + Framebuffer, + PixelDatatype, + Texture) { + 'use strict'; + + /** + * Checks if the context's floating point textures support 6 decimal places of precision. + * + * @param {Context} context A context wrapping a gl implementation. + * @returns {Boolean} Whether or not the context's floating point textures support 6 decimal places of precision + * + * @private + */ + function checkFloatTexturePrecision(context) { + if (!context.floatingPointTexture) { + return false; + } + + var computeEngine = new ComputeEngine(context); + var outputTexture = new Texture({ + context : context, + width : 1, + height : 1, + pixelFormat : PixelFormat.RGBA + }); + + var floatTexture = new Texture({ + context : context, + width : 1, + height : 1, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : checkFloatTexturePrecision._getFloatPixelType(), + source : { + width : 1, + height : 1, + arrayBufferView : checkFloatTexturePrecision._getArray([123456, 0, 0, 0]) + } + }); + + var framebuffer = new Framebuffer({ + context : context, + colorTextures : [outputTexture], + destroyAttachments : false + }); + + var readState = { + framebuffer : framebuffer, + x : 0, + y : 0, + width : 1, + height : 1 + }; + + var sixPlaces = false; + var computeCommand = new ComputeCommand({ + fragmentShaderSource : CheckFloatTexturePrecisionFS, + outputTexture : outputTexture, + uniformMap : { + u_floatTexture : function() { + return floatTexture; + } + }, + persists : false, + postExecute : function() { + var pixel = context.readPixels(readState); + sixPlaces = pixel[0] === 0; + } + }); + + computeCommand.execute(computeEngine); + + computeEngine.destroy(); + framebuffer.destroy(); + + return sixPlaces; + } + + checkFloatTexturePrecision._getFloatPixelType = function() { + return PixelDatatype.FLOAT; + }; + + checkFloatTexturePrecision._getArray = function(array) { + return new Float32Array(array); + }; + + return checkFloatTexturePrecision; +}); diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 0d9be7b8880a..7218167cade9 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -578,7 +578,7 @@ define([ }); var attributeLocations = classificationPrimitive._primitive._attributeLocations; - var shadowVolumeAppearance = new ShadowVolumeAppearance(cullFragmentsUsingExtents, planarExtents, classificationPrimitive.appearance); + var shadowVolumeAppearance = new ShadowVolumeAppearance(cullFragmentsUsingExtents, planarExtents, classificationPrimitive.appearance, context.floatTextureSixPlaces); classificationPrimitive._spStencil = ShaderProgram.replaceCache({ context : context, diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 905ea01005bb..eb09ab4561d1 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -730,10 +730,11 @@ define([ var boundingRectangle = getRectangle(frameState, geometry); var textureCoordinateRotationPoints = geometry.textureCoordinateRotationPoints; + var useFloatBatchTable = frameState.context.floatTextureSixPlaces; if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection, this._maxHeight); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection, useFloatBatchTable, this._maxHeight); } else { - attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection); + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection, useFloatBatchTable); } var instanceAttributes = instance.attributes; diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index ea9c6e0ba5f6..3bf2997ec2e7 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -49,15 +49,19 @@ define([ * @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) { + 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._useFloatBatchTable = useFloatBatchTable; + // Compute shader dependencies var colorShaderDependencies = new ShaderDependencies(); colorShaderDependencies.requiresTextureCoordinates = extentsCulling; @@ -200,7 +204,7 @@ define([ Check.typeOf.bool('columbusView2D', columbusView2D); Check.defined('mapProjection', mapProjection); //>>includeEnd('debug'); - return createShadowVolumeAppearanceVS(this._colorShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, this._appearance, mapProjection); + return createShadowVolumeAppearanceVS(this._colorShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, this._appearance, mapProjection, this._useFloatBatchTable); }; /** @@ -219,7 +223,7 @@ define([ Check.typeOf.bool('columbusView2D', columbusView2D); Check.defined('mapProjection', mapProjection); //>>includeEnd('debug'); - return createShadowVolumeAppearanceVS(this._pickShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, undefined, mapProjection); + return createShadowVolumeAppearanceVS(this._pickShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, undefined, mapProjection, this._useFloatBatchTable); }; var longitudeExtentsCartesianScratch = new Cartesian3(); @@ -228,7 +232,7 @@ define([ high : 0.0, low : 0.0 }; - function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance, mapProjection) { + function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance, mapProjection, useFloatBatchTable) { var allDefines = defines.slice(); if (projectionExtentDefines.eastMostYhighDefine === '') { @@ -271,6 +275,10 @@ define([ } } + if (!useFloatBatchTable) { + allDefines.push('UINT8_PACKING'); + } + return new ShaderSource({ defines : allDefines, sources : [vertexShaderSource] @@ -403,12 +411,81 @@ define([ }); } + 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) { + function add2DTextureCoordinateAttributes(rectangle, projection, attributes, useFloatBatchTable) { // Compute corner positions in double precision var carto = cartographicScratch; carto.height = 0.0; @@ -432,9 +509,30 @@ define([ // 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]; - var encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch); + encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch); valuesHigh[0] = encoded.high; valuesLow[0] = encoded.low; @@ -578,15 +676,17 @@ define([ * @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, height) { + 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; @@ -598,6 +698,30 @@ define([ 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, @@ -623,7 +747,7 @@ define([ value : Cartesian3.pack(northward, [0, 0, 0]) }); - add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); + add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes, true); return attributes; }; @@ -667,14 +791,16 @@ define([ * @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) { + 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 @@ -721,21 +847,48 @@ define([ }; addTextureCoordinateRotationAttributes(attributes, textureCoordinateRotationPoints); - add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); + add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes, useFloatBatchTable); return attributes; }; ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function(attributes) { - return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && + 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) { - return defined(attributes.sphericalExtents) && defined(attributes.longitudeRotation) && - defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && - defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); + 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) { diff --git a/Source/Shaders/CheckFloatTexturePrecisionFS.glsl b/Source/Shaders/CheckFloatTexturePrecisionFS.glsl new file mode 100644 index 000000000000..8f77d167cef5 --- /dev/null +++ b/Source/Shaders/CheckFloatTexturePrecisionFS.glsl @@ -0,0 +1,8 @@ +uniform sampler2D u_floatTexture; + +void main() +{ + float actual = texture2D(u_floatTexture, vec2(0.5, 0.5)).r; + float expected = 123456.0; + gl_FragColor = vec4(abs(actual - expected), 0.0, 0.0, 1.0); +} diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index ab8903295863..d42c278e5111 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -20,12 +20,14 @@ varying vec4 v_color; #endif #ifdef NORMAL_EC -vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) { +vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) +{ vec4 eyeCoordinate = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth); return eyeCoordinate.xyz / eyeCoordinate.w; } -vec3 vectorFromOffset(vec4 eyeCoordinate, vec2 positiveOffset) { +vec3 vectorFromOffset(vec4 eyeCoordinate, vec2 positiveOffset) +{ vec2 glFragCoordXY = gl_FragCoord.xy; // Sample depths at both offset and negative offset float upOrRightLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw)); @@ -71,7 +73,8 @@ void main(void) #ifdef PICK #ifdef CULL_FRAGMENTS - if (0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0) { + if (0.0 <= uv.x && uv.x <= 1.0 && 0.0 <= uv.y && uv.y <= 1.0) + { gl_FragColor.a = 1.0; // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource czm_writeDepthClampedToFarPlane(); } @@ -81,7 +84,8 @@ void main(void) #else // PICK #ifdef CULL_FRAGMENTS - if (uv.x <= 0.0 || 1.0 <= uv.x || uv.y <= 0.0 || 1.0 <= uv.y) { + if (uv.x <= 0.0 || 1.0 <= uv.x || uv.y <= 0.0 || 1.0 <= uv.y) + { discard; } #endif diff --git a/Source/Shaders/ShadowVolumeAppearanceVS.glsl b/Source/Shaders/ShadowVolumeAppearanceVS.glsl index 0f2b1c456a3b..b5d486c7de68 100644 --- a/Source/Shaders/ShadowVolumeAppearanceVS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceVS.glsl @@ -25,6 +25,88 @@ varying vec3 v_uMaxAndInverseDistance; varying vec3 v_vMaxAndInverseDistance; #endif // TEXTURE_COORDINATES +#if defined(TEXTURE_COORDINATES) && !defined(SPHERICAL) && defined(UINT8_PACKING) +vec4 clampAndMagnitude(vec4 sd) +{ + vec4 d = sd; + d.x = czm_branchFreeTernary(sd.x < 128.0, d.x, (255.0 - sd.x)); + d.x = floor(0.5 + d.x); + d.y = floor(0.5 + d.y); + d.z = floor(0.5 + d.z); + d.w = floor(0.5 + d.w); + return d; +} + +float unpackLowLessThan100k(vec4 sd) +{ + vec4 d = clampAndMagnitude(sd); + return (1000.0 * d.x + 10.0 * d.y + 0.1 * d.z + 0.001 * d.w) * czm_branchFreeTernary(sd.x < 128.0, 1.0, -1.0); +} + +vec3 southwest_LOW(vec4 x, vec4 y, vec4 z) +{ + vec3 value; + value.x = unpackLowLessThan100k(x); + value.y = unpackLowLessThan100k(y); + value.z = unpackLowLessThan100k(z); + return value; +} + +float unpackHighMagLessThan100Million(vec4 sd) +{ + vec4 d = clampAndMagnitude(sd); + return (1000000.0 * d.x + 10000.0 * d.y + 100.0 * d.z + d.w) * czm_branchFreeTernary(sd.x < 128.0, 1.0, -1.0); +} + +vec3 southwest_HIGH(vec4 x, vec4 y, vec4 z) +{ + vec3 value; + value.x = unpackHighMagLessThan100Million(x); + value.y = unpackHighMagLessThan100Million(y); + value.z = unpackHighMagLessThan100Million(z); + return value; +} + +#ifdef COLUMBUS_VIEW_2D +vec4 unpackPlanes2D_HIGH(vec4 x, vec4 y, vec4 z, vec4 w) +{ + vec4 value; + value.x = unpackHighMagLessThan100Million(x); + value.y = unpackHighMagLessThan100Million(y); + value.z = unpackHighMagLessThan100Million(z); + value.w = unpackHighMagLessThan100Million(w); + return value; +} + +vec4 unpackPlanes2D_LOW(vec4 x, vec4 y, vec4 z, vec4 w) +{ + vec4 value; + value.x = unpackLowLessThan100k(x); + value.y = unpackLowLessThan100k(y); + value.z = unpackLowLessThan100k(z); + value.w = unpackLowLessThan100k(w); + return value; +} + +#else +float unpackLowLessThan1000k(vec4 sd) +{ + vec4 d = clampAndMagnitude(sd); + return (10000.0 * d.x + 100.0 * d.y + d.z + 0.01 * d.w) * czm_branchFreeTernary(sd.x < 128.0, 1.0, -1.0); +} + +vec3 unpackExtent(vec4 x, vec4 y, vec4 z) +{ + vec3 value; + value.x = unpackLowLessThan1000k(x); + value.y = unpackLowLessThan1000k(y); + value.z = unpackLowLessThan1000k(z); + return value; +} + +#endif +#endif + void main() { vec4 position = czm_computePosition(); @@ -43,8 +125,19 @@ void main() v_uvMinAndSphericalLongitudeRotation.z = czm_batchTable_longitudeRotation(batchId); #else // SPHERICAL #ifdef COLUMBUS_VIEW_2D +#ifdef UINT8_PACKING + vec4 planes2D_high = unpackPlanes2D_HIGH(czm_batchTable_planes2D_HIGH_x(batchId), + czm_batchTable_planes2D_HIGH_y(batchId), + czm_batchTable_planes2D_HIGH_z(batchId), + czm_batchTable_planes2D_HIGH_w(batchId)); + vec4 planes2D_low = unpackPlanes2D_LOW(czm_batchTable_planes2D_LOW_x(batchId), + czm_batchTable_planes2D_LOW_y(batchId), + czm_batchTable_planes2D_LOW_z(batchId), + czm_batchTable_planes2D_LOW_w(batchId)); +#else // UINT8_PACKING vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId); vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId); +#endif // UINT8_PACKING // If the primitive is split across the IDL (planes2D_high.x > planes2D_high.w): // - If this vertex is on the east side of the IDL (position3DLow.y > 0.0, comparison with position3DHigh may produce artifacts) @@ -68,9 +161,24 @@ void main() vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.w, planes2D_high.y), vec3(0.0, planes2D_low.w, planes2D_low.y))).xyz; #else // COLUMBUS_VIEW_2D // 3D case has smaller "plane extents," so planes encoded as a 64 bit position and 2 vec3s for distances/direction +#ifdef UINT8_PACKING + vec3 low = southwest_LOW(czm_batchTable_southWest_LOW_x(batchId), czm_batchTable_southWest_LOW_y(batchId), czm_batchTable_southWest_LOW_z(batchId)); + vec3 high = southwest_HIGH(czm_batchTable_southWest_HIGH_x(batchId), czm_batchTable_southWest_HIGH_y(batchId), czm_batchTable_southWest_HIGH_z(batchId)); + vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(high, low)).xyz; + vec3 northWestCorner = czm_normal * unpackExtent( + czm_batchTable_northward_x(batchId), + czm_batchTable_northward_y(batchId), + czm_batchTable_northward_z(batchId)) + southWestCorner; + + vec3 southEastCorner = czm_normal * unpackExtent( + czm_batchTable_eastward_x(batchId), + czm_batchTable_eastward_y(batchId), + czm_batchTable_eastward_z(batchId)) + southWestCorner; +#else // UINT8_PACKING vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz; vec3 northWestCorner = czm_normal * czm_batchTable_northward(batchId) + southWestCorner; vec3 southEastCorner = czm_normal * czm_batchTable_eastward(batchId) + southWestCorner; +#endif // UINT8_PACKING #endif // COLUMBUS_VIEW_2D vec3 eastWard = southEastCorner - southWestCorner; diff --git a/Specs/Renderer/ContextSpec.js b/Specs/Renderer/ContextSpec.js index d395ca7a7daf..273967a07865 100644 --- a/Specs/Renderer/ContextSpec.js +++ b/Specs/Renderer/ContextSpec.js @@ -173,6 +173,10 @@ describe('Renderer/Context', function() { expect(context.floatingPointTexture).toBeDefined(); }); + it('gets whether the texture float has 6 places of precision', function() { + expect(context.floatTextureSixPlaces).toBeDefined(); + }); + it('gets texture filter anisotropic extension', function() { expect(context.textureFilterAnisotropic).toBeDefined(); }); diff --git a/Specs/Renderer/checkFloatTexturePrecisionSpec.js b/Specs/Renderer/checkFloatTexturePrecisionSpec.js new file mode 100644 index 000000000000..c9224ed87f99 --- /dev/null +++ b/Specs/Renderer/checkFloatTexturePrecisionSpec.js @@ -0,0 +1,42 @@ +define([ + 'Renderer/checkFloatTexturePrecision', + 'Renderer/PixelDatatype', + 'Specs/createContext' + ], function( + checkFloatTexturePrecision, + PixelDatatype, + createContext) { + 'use strict'; + +describe('Renderer/checkFloatTexturePrecision', function() { + + var context; + + beforeAll(function() { + context = createContext(); + }); + + afterAll(function() { + context.destroyForSpecs(); + }); + + it('returns false when float textures are not available', function() { + expect(checkFloatTexturePrecision({ floatingPointTexture : false })).toBe(false); + }); + + it('returns false when float textures are of insufficient precision', function() { + if (!context.floatingPointTexture) { + return; + } + + spyOn(checkFloatTexturePrecision, '_getFloatPixelType').and.callFake(function() { + return PixelDatatype.HALF_FLOAT; + }); + spyOn(checkFloatTexturePrecision, '_getArray').and.callFake(function(array) { + return new Uint16Array(array); + }); + + expect(checkFloatTexturePrecision(context)).toBe(false); + }); +}, 'WebGL'); +}); diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index 85936024fa8e..3b261cf41e2c 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -47,8 +47,10 @@ describe('Scene/ShadowVolumeAppearance', function() { var largeTestRectangle = Rectangle.fromDegrees(-45.0, -45.0, 45.0, 45.0); var smallTestRectangle = Rectangle.fromDegrees(-0.1, -0.1, 0.1, 0.1); - var largeRectangleAttributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(largeTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection); - var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection); + var largeRectangleAttributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(largeTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection, true); + var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection, true); + var largeRectangleAttributesBadFloats = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(largeTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection, false); + var smallRectangleAttributesBadFloats = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, [0, 0, 0, 1, 1, 0], unitSphereEllipsoid, projection, false); var perInstanceColorMaterialAppearance = new PerInstanceColorAppearance(); var flatPerInstanceColorMaterialAppearance = new PerInstanceColorAppearance({ @@ -123,88 +125,219 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(attribute.normalize).toEqual(false); } - it('provides attributes for computing texture coordinates using planes in 3D', function() { - var attributes = smallRectangleAttributes; - - var southWest_LOW = attributes.southWest_LOW; - var southWest_HIGH = attributes.southWest_HIGH; - var eastward = attributes.eastward; - var northward = attributes.northward; - - checkGeometryInstanceAttributeVec3(southWest_LOW); - checkGeometryInstanceAttributeVec3(southWest_HIGH); - checkGeometryInstanceAttributeVec3(eastward); - checkGeometryInstanceAttributeVec3(northward); - - // We're using a unit sphere, so expect all HIGH values to be basically 0 - // and LOW value to be within a small cone around UNIT_X - expect(southWest_HIGH.value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(southWest_HIGH.value[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(southWest_HIGH.value[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + function clampAndMagnitude(signedVec4Attribute) { + var signedVec4 = signedVec4Attribute.value; + var unsigned = signedVec4.slice(); + unsigned[0] = signedVec4[0] < 128.0 ? signedVec4[0] : (255.0 - signedVec4[0]); + unsigned[0] = Math.floor(0.5 + unsigned[0]); + unsigned[1] = Math.floor(0.5 + unsigned[1]); + unsigned[2] = Math.floor(0.5 + unsigned[2]); + unsigned[3] = Math.floor(0.5 + unsigned[3]); + return unsigned; + } - expect(southWest_LOW.value[0]).toBeGreaterThan(Math.cos(CesiumMath.toRadians(0.2))); + function unpackLowLessThan100k(signedVec4Attribute) { + var signed = signedVec4Attribute.value; + var unsigned = clampAndMagnitude(signedVec4Attribute); + return (1000.0 * unsigned[0] + 10.0 * unsigned[1] + 0.1 * unsigned[2] + 0.001 * unsigned[3]) * (signed[0] < 128.0 ? 1.0 : -1.0); + } - // Expect eastward and northward to be unit-direction vectors in the ENU coordinate system at the rectangle center - var smallRectangleCenter = Cartographic.toCartesian(Rectangle.center(smallTestRectangle), unitSphereEllipsoid); - var enuMatrix = Transforms.eastNorthUpToFixedFrame(smallRectangleCenter, unitSphereEllipsoid); - var inverseEnu = Matrix4.inverse(enuMatrix, new Matrix4()); + function unpackHighMagLessThan100Million(signedVec4Attribute) { + var signed = signedVec4Attribute.value; + var unsigned = clampAndMagnitude(signedVec4Attribute); + return (1000000.0 * unsigned[0] + 10000.0 * unsigned[1] + 100.0 * unsigned[2] + unsigned[3]) * (signed[0] < 128.0 ? 1.0 : -1.0); + } - var eastwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(eastward.value), new Cartesian3()); - eastwardENU = Cartesian3.normalize(eastwardENU, eastwardENU); - expect(Cartesian3.equalsEpsilon(eastwardENU, Cartesian3.UNIT_X, CesiumMath.EPSILON7)).toBe(true); + function unpackLowLessThan1000k(signedVec4Attribute) { + var signed = signedVec4Attribute.value; + var unsigned = clampAndMagnitude(signedVec4Attribute); + return (10000.0 * unsigned[0] + 100.0 * unsigned[1] + unsigned[2] + 0.01 * unsigned[3]) * (signed[0] < 128.0 ? 1.0 : -1.0); + } - var northwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(northward.value), new Cartesian3()); - northwardENU = Cartesian3.normalize(northwardENU, northwardENU); - expect(Cartesian3.equalsEpsilon(northwardENU, Cartesian3.UNIT_Y, CesiumMath.EPSILON7)).toBe(true); + describe('floating point textures reliable', function () { + it('provides attributes for computing texture coordinates using planes in 3D', function() { + var attributes = smallRectangleAttributes; + + var southWest_LOW = attributes.southWest_LOW; + var southWest_HIGH = attributes.southWest_HIGH; + var eastward = attributes.eastward; + var northward = attributes.northward; + + checkGeometryInstanceAttributeVec3(southWest_LOW); + checkGeometryInstanceAttributeVec3(southWest_HIGH); + checkGeometryInstanceAttributeVec3(eastward); + checkGeometryInstanceAttributeVec3(northward); + + // We're using a unit sphere, so expect all HIGH values to be basically 0 + // and LOW value to be within a small cone around UNIT_X + expect(southWest_HIGH.value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(southWest_HIGH.value[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(southWest_HIGH.value[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + + expect(southWest_LOW.value[0]).toBeGreaterThan(Math.cos(CesiumMath.toRadians(0.2))); + + // Expect eastward and northward to be unit-direction vectors in the ENU coordinate system at the rectangle center + var smallRectangleCenter = Cartographic.toCartesian(Rectangle.center(smallTestRectangle), unitSphereEllipsoid); + var enuMatrix = Transforms.eastNorthUpToFixedFrame(smallRectangleCenter, unitSphereEllipsoid); + var inverseEnu = Matrix4.inverse(enuMatrix, new Matrix4()); + + var eastwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(eastward.value), new Cartesian3()); + eastwardENU = Cartesian3.normalize(eastwardENU, eastwardENU); + expect(Cartesian3.equalsEpsilon(eastwardENU, Cartesian3.UNIT_X, CesiumMath.EPSILON7)).toBe(true); + + var northwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(northward.value), new Cartesian3()); + northwardENU = Cartesian3.normalize(northwardENU, northwardENU); + expect(Cartesian3.equalsEpsilon(northwardENU, Cartesian3.UNIT_Y, CesiumMath.EPSILON7)).toBe(true); + }); + + it('provides attributes for computing planes in 2D and Columbus View', function() { + var planes2D_HIGH = largeRectangleAttributes.planes2D_HIGH; + var planes2D_LOW = largeRectangleAttributes.planes2D_LOW; + + expect(planes2D_HIGH.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(planes2D_HIGH.componentsPerAttribute).toEqual(4); + expect(planes2D_HIGH.normalize).toEqual(false); + + // Because using a unit sphere expect all HIGH values to be basically 0 + var highValue = planes2D_HIGH.value; + expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + + expect(planes2D_LOW.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(planes2D_LOW.componentsPerAttribute).toEqual(4); + expect(planes2D_LOW.normalize).toEqual(false); + + var cartographic = Cartographic.fromDegrees(-45, -45, 0.0); // southwest corner + var southwestCartesian = projection.project(cartographic); + var lowValue = planes2D_LOW.value; + expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON7); + expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON7); + expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON7); + expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON7); + + // Small case + // Because using a unit sphere expect all HIGH values to be basically 0 + highValue = smallRectangleAttributes.planes2D_HIGH.value; + expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + + cartographic = Cartographic.fromDegrees(-0.1, -0.1, 0.0); // southwest corner + southwestCartesian = projection.project(cartographic); + lowValue = smallRectangleAttributes.planes2D_LOW.value; + expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON7); + expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON7); + expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON7); + expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON7); + }); }); - it('provides attributes for computing planes in 2D and Columbus View', function() { - var planes2D_HIGH = largeRectangleAttributes.planes2D_HIGH; - var planes2D_LOW = largeRectangleAttributes.planes2D_LOW; - - expect(planes2D_HIGH.componentDatatype).toEqual(ComponentDatatype.FLOAT); - expect(planes2D_HIGH.componentsPerAttribute).toEqual(4); - expect(planes2D_HIGH.normalize).toEqual(false); - - // Because using a unit sphere expect all HIGH values to be basically 0 - var highValue = planes2D_HIGH.value; - expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - - expect(planes2D_LOW.componentDatatype).toEqual(ComponentDatatype.FLOAT); - expect(planes2D_LOW.componentsPerAttribute).toEqual(4); - expect(planes2D_LOW.normalize).toEqual(false); - - var cartographic = Cartographic.fromDegrees(-45, -45, 0.0); // southwest corner - var southwestCartesian = projection.project(cartographic); - var lowValue = planes2D_LOW.value; - expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON7); - expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON7); - expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON7); - expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON7); - - // Small case - // Because using a unit sphere expect all HIGH values to be basically 0 - highValue = smallRectangleAttributes.planes2D_HIGH.value; - expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - - cartographic = Cartographic.fromDegrees(-0.1, -0.1, 0.0); // southwest corner - southwestCartesian = projection.project(cartographic); - lowValue = smallRectangleAttributes.planes2D_LOW.value; - expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON7); - expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON7); - expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON7); - expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON7); + describe('floating point textures unreliable', function () { + it('provides attributes for computing texture coordinates using planes in 3D', function() { + var attributes = smallRectangleAttributesBadFloats; + + var southWest_LOW = [0, 0, 0]; + southWest_LOW[0] = unpackLowLessThan100k(attributes.southWest_LOW_x); + southWest_LOW[1] = unpackLowLessThan100k(attributes.southWest_LOW_y); + southWest_LOW[2] = unpackLowLessThan100k(attributes.southWest_LOW_z); + + var southWest_HIGH = [0, 0, 0]; + southWest_HIGH[0] = unpackHighMagLessThan100Million(attributes.southWest_HIGH_x); + southWest_HIGH[1] = unpackHighMagLessThan100Million(attributes.southWest_HIGH_y); + southWest_HIGH[2] = unpackHighMagLessThan100Million(attributes.southWest_HIGH_z); + + var eastward = [0, 0, 0]; + eastward[0] = unpackLowLessThan1000k(attributes.eastward_x); + eastward[1] = unpackLowLessThan1000k(attributes.eastward_y); + eastward[2] = unpackLowLessThan1000k(attributes.eastward_z); + + var northward = [0, 0, 0]; + northward[0] = unpackLowLessThan1000k(attributes.northward_x); + northward[1] = unpackLowLessThan1000k(attributes.northward_y); + northward[2] = unpackLowLessThan1000k(attributes.northward_z); + + // We're using a unit sphere, so expect all HIGH values to be basically 0 + // and LOW value to be within a small cone around UNIT_X + expect(southWest_HIGH[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(southWest_HIGH[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(southWest_HIGH[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + + expect(southWest_LOW[0]).toBeGreaterThan(Math.cos(CesiumMath.toRadians(0.2))); + + // Expect eastward and northward to be unit-direction vectors in the ENU coordinate system at the rectangle center + var smallRectangleCenter = Cartographic.toCartesian(Rectangle.center(smallTestRectangle), unitSphereEllipsoid); + var enuMatrix = Transforms.eastNorthUpToFixedFrame(smallRectangleCenter, unitSphereEllipsoid); + var inverseEnu = Matrix4.inverse(enuMatrix, new Matrix4()); + + var eastwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(eastward), new Cartesian3()); + eastwardENU = Cartesian3.normalize(eastwardENU, eastwardENU); + expect(Cartesian3.equalsEpsilon(eastwardENU, Cartesian3.UNIT_X, CesiumMath.EPSILON2)).toBe(true); + + var northwardENU = Matrix4.multiplyByPointAsVector(inverseEnu, Cartesian3.fromArray(northward), new Cartesian3()); + northwardENU = Cartesian3.normalize(northwardENU, northwardENU); + expect(Cartesian3.equalsEpsilon(northwardENU, Cartesian3.UNIT_Y, CesiumMath.EPSILON2)).toBe(true); + }); + + it('provides attributes for computing planes in 2D and Columbus View', function() { + var attributes = largeRectangleAttributesBadFloats; + var highValue = [0, 0, 0, 0]; + highValue[0] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_x); + highValue[1] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_y); + highValue[2] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_z); + highValue[3] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_w); + + var lowValue = [0, 0, 0, 0]; + lowValue[0] = unpackLowLessThan100k(attributes.planes2D_LOW_x); + lowValue[1] = unpackLowLessThan100k(attributes.planes2D_LOW_y); + lowValue[2] = unpackLowLessThan100k(attributes.planes2D_LOW_z); + lowValue[3] = unpackLowLessThan100k(attributes.planes2D_LOW_w); + + // Because using a unit sphere expect all HIGH values to be basically 0 + expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + + var cartographic = Cartographic.fromDegrees(-45, -45, 0.0); // southwest corner + var southwestCartesian = projection.project(cartographic); + expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON2); + expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON2); + expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON2); + expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON2); + + // Small case + attributes = smallRectangleAttributesBadFloats; + // Because using a unit sphere expect all HIGH values to be basically 0 + highValue[0] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_x); + highValue[1] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_y); + highValue[2] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_z); + highValue[3] = unpackHighMagLessThan100Million(attributes.planes2D_HIGH_w); + + expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON2); + + cartographic = Cartographic.fromDegrees(-0.1, -0.1, 0.0); // southwest corner + southwestCartesian = projection.project(cartographic); + lowValue[0] = unpackLowLessThan100k(attributes.planes2D_LOW_x); + lowValue[1] = unpackLowLessThan100k(attributes.planes2D_LOW_y); + lowValue[2] = unpackLowLessThan100k(attributes.planes2D_LOW_z); + lowValue[3] = unpackLowLessThan100k(attributes.planes2D_LOW_w); + + expect(lowValue[0]).toEqualEpsilon(southwestCartesian.x, CesiumMath.EPSILON2); + expect(lowValue[1]).toEqualEpsilon(southwestCartesian.y, CesiumMath.EPSILON2); + expect(lowValue[2]).toEqualEpsilon(-southwestCartesian.y, CesiumMath.EPSILON2); + expect(lowValue[3]).toEqualEpsilon(-southwestCartesian.x, CesiumMath.EPSILON2); + }); }); it('provides attributes for rotating texture coordinates', function() { // 90 degree rotation of a square, so "max" in Y direction is (0,0), "max" in X direction is (1,1) - var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, [1, 0, 0, 0, 1, 1], unitSphereEllipsoid, projection, 0.0); + var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, [1, 0, 0, 0, 1, 1], unitSphereEllipsoid, projection, false, 0.0); var uMaxVmax = attributes.uMaxVmax; var uvMinAndExtents = attributes.uvMinAndExtents; @@ -233,11 +366,15 @@ describe('Scene/ShadowVolumeAppearance', function() { it('checks for spherical extent attributes', function() { expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(smallRectangleAttributes)).toBe(false); expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(largeRectangleAttributes)).toBe(true); + expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(smallRectangleAttributesBadFloats)).toBe(false); + expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(largeRectangleAttributesBadFloats)).toBe(true); }); it('checks for planar texture coordinate attributes', function() { expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(smallRectangleAttributes)).toBe(true); expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(largeRectangleAttributes)).toBe(false); + expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(smallRectangleAttributesBadFloats)).toBe(true); + expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(largeRectangleAttributesBadFloats)).toBe(false); }); it('checks if a rectangle should use spherical texture coordinates', function() { @@ -245,9 +382,36 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(ShadowVolumeAppearance.shouldUseSphericalCoordinates(largeTestRectangle)).toBe(true); }); + it('creates vertex shaders based on whether or not float textures are used for high/low positions', function() { + // Check defines + var sphericalTexturedAppearanceUnsafe = new ShadowVolumeAppearance(true, false, textureMaterialAppearance, false); + var shaderSource = sphericalTexturedAppearanceUnsafe.createVertexShader([], testVs, false, projection); + var defines = shaderSource.defines; + expect(defines.length).toEqual(3); + expect(defines.indexOf('UINT8_PACKING')).not.toEqual(-1); + + // 2D variant + shaderSource = sphericalTexturedAppearanceUnsafe.createVertexShader([], testVs, true, projection); + defines = shaderSource.defines; + expect(defines.length).toEqual(7); + expect(defines.indexOf('UINT8_PACKING')).not.toEqual(-1); + + var sphericalTexturedAppearanceSafe = new ShadowVolumeAppearance(true, false, textureMaterialAppearance, true); + shaderSource = sphericalTexturedAppearanceSafe.createVertexShader([], testVs, false, projection); + defines = shaderSource.defines; + expect(defines.length).toEqual(2); + expect(defines.indexOf('UINT8_PACKING')).toEqual(-1); + + // 2D variant + shaderSource = sphericalTexturedAppearanceSafe.createVertexShader([], testVs, true, projection); + defines = shaderSource.defines; + expect(defines.length).toEqual(6); + expect(defines.indexOf('UINT8_PACKING')).toEqual(-1); + }); + it('creates vertex shaders for color', function() { // Check defines - var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance, true); var shaderSource = sphericalTexturedAppearance.createVertexShader([], testVs, false, projection); var defines = shaderSource.defines; expect(defines.length).toEqual(2); @@ -267,7 +431,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1); // Unculled color appearance - no texcoords at all - var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance); + var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance, true); shaderSource = sphericalUnculledColorAppearance.createVertexShader([], testVs, false, projection); defines = shaderSource.defines; expect(defines.length).toEqual(1); @@ -286,7 +450,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.indexOf('PER_INSTANCE_COLOR')).not.toEqual(-1); // Planar textured, without culling - var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance); + var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance, true); shaderSource = planarTexturedAppearance.createVertexShader([], testVs, false, projection); defines = shaderSource.defines; expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); @@ -307,7 +471,7 @@ describe('Scene/ShadowVolumeAppearance', function() { it('creates vertex shaders for pick', function() { // Check defines - var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance, true); var shaderSource = sphericalTexturedAppearance.createPickVertexShader([], testVs, false, projection); var defines = shaderSource.defines; expect(defines.length).toEqual(2); @@ -327,7 +491,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1); // Unculled color appearance - no texcoords at all - var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance); + var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance, true); shaderSource = sphericalUnculledColorAppearance.createPickVertexShader([], testVs, false, projection); defines = shaderSource.defines; expect(defines.length).toEqual(0); @@ -344,7 +508,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.length).toEqual(4); // Planar textured, without culling - var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance); + var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance, true); shaderSource = planarTexturedAppearance.createPickVertexShader([], testVs, false, projection); defines = shaderSource.defines; expect(defines.length).toEqual(0); @@ -362,7 +526,7 @@ describe('Scene/ShadowVolumeAppearance', function() { it('creates fragment shaders for color and pick', function() { // Check defines - var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance, true); var shaderSource = sphericalTexturedAppearance.createFragmentShader(false); var defines = shaderSource.defines; @@ -395,7 +559,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.length).toEqual(9); // Culling with planar texture coordinates on a per-color material - var planarColorAppearance = new ShadowVolumeAppearance(true, true, perInstanceColorMaterialAppearance); + var planarColorAppearance = new ShadowVolumeAppearance(true, true, perInstanceColorMaterialAppearance, true); shaderSource = planarColorAppearance.createFragmentShader(false); defines = shaderSource.defines; @@ -419,7 +583,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.length).toEqual(5); // Flat - var flatSphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, flatTextureMaterialAppearance); + var flatSphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, flatTextureMaterialAppearance, true); shaderSource = flatSphericalTexturedAppearance.createFragmentShader(false); defines = shaderSource.defines; expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); @@ -434,7 +598,7 @@ describe('Scene/ShadowVolumeAppearance', function() { expect(defines.indexOf('FLAT')).not.toEqual(-1); expect(defines.length).toEqual(10); - var flatSphericalColorAppearance = new ShadowVolumeAppearance(false, false, flatPerInstanceColorMaterialAppearance); + var flatSphericalColorAppearance = new ShadowVolumeAppearance(false, false, flatPerInstanceColorMaterialAppearance, true); shaderSource = flatSphericalColorAppearance.createFragmentShader(false); defines = shaderSource.defines; expect(defines.indexOf('SPHERICAL')).not.toEqual(-1);