From bdf302eec8f00853e4e8015143a0620098eaa761 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 13 Feb 2018 17:38:13 -0500 Subject: [PATCH 01/40] able to get world coordinates from depth tex --- Apps/Sandcastle/gallery/testDepth.html | 137 ++++++++++++++++++++++++ Source/Scene/ClassificationPrimitive.js | 8 +- Source/Scene/GlobeDepth.js | 10 +- Source/Shaders/ShadowVolumeFS.glsl | 53 ++++++++- 4 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 Apps/Sandcastle/gallery/testDepth.html diff --git a/Apps/Sandcastle/gallery/testDepth.html b/Apps/Sandcastle/gallery/testDepth.html new file mode 100644 index 00000000000..cd08715d737 --- /dev/null +++ b/Apps/Sandcastle/gallery/testDepth.html @@ -0,0 +1,137 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index c4ec4affb04..eba76b90f59 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -22,7 +22,8 @@ define([ './Primitive', './SceneMode', './StencilFunction', - './StencilOperation' + './StencilOperation', + '../Core/WebGLConstants' ], function( ColorGeometryInstanceAttribute, defaultValue, @@ -47,7 +48,8 @@ define([ Primitive, SceneMode, StencilFunction, - StencilOperation) { + StencilOperation, + WebGLConstants) { 'use strict'; var ClassificationPrimitiveReadOnlyInstanceAttributes = ['color']; @@ -594,7 +596,7 @@ define([ function createColorCommands(classificationPrimitive, colorCommands) { var primitive = classificationPrimitive._primitive; - var length = primitive._va.length * 3; + var length = primitive._va.length * 3; // each geometry (pack of vertex attributes) needs 3 commands: front/back stencils and fill colorCommands.length = length; var i; diff --git a/Source/Scene/GlobeDepth.js b/Source/Scene/GlobeDepth.js index 04369056877..835c4d4f528 100644 --- a/Source/Scene/GlobeDepth.js +++ b/Source/Scene/GlobeDepth.js @@ -56,11 +56,11 @@ define([ 'void main()\n' + '{\n' + ' float z_window = czm_unpackDepth(texture2D(u_texture, v_textureCoordinates));\n' + - ' float n_range = czm_depthRange.near;\n' + - ' float f_range = czm_depthRange.far;\n' + - ' float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' + - ' float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' + - ' gl_FragColor = vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' + + ' //float n_range = czm_depthRange.near;\n' + + ' //float f_range = czm_depthRange.far;\n' + + ' //float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' + + ' //float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' + + ' gl_FragColor = vec4(vec3(z_window), 1.0);// vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' + '}\n'; globeDepth._debugGlobeDepthViewportCommand = context.createViewportQuadCommand(fs, { diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index 51e951b07b9..941fb57df59 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -8,12 +8,63 @@ uniform vec4 u_highlightColor; varying vec4 v_color; #endif +/* +vec3 drawingBufferToWgs84Coordinates(vec2 drawingBufferPosition, float depth) { + vec4 ndc; + ndc.x = (drawingBufferPosition.x - czm_viewport.x) / czm_viewport.z * 2.0 - 1.0; + ndc.y = (drawingBufferPosition.y - czm_viewport.y) / czm_viewport.w * 2.0 - 1.0; + ndc.z = (depth * 2.0) - 1.0; + ndc.w = 1.0; + + if (czm_inverseViewProjection != mat4(0.0)) { + vec4 worldCoords = czm_inverseViewProjection * ndc; + // Reverse perspective divide? + float w = 1.0 / worldCoords.w; + worldCoords.xyz * w; + return worldCoords.xyz; + } else { + // TODO: debug me toooo... + float top = czm_frustumPlanes.x; + float bottom = czm_frustumPlanes.y; + float left = czm_frustumPlanes.z; + float right = czm_frustumPlanes.w; + + float near = czm_currentFrustum.x; + float far = czm_currentFrustum.y; + + vec4 worldCoords; + worldCoords.x = (ndc.x * (right - left) + left + right) * 0.5; + worldCoords.y = (ndc.y * (top - bottom) + bottom + top) * 0.5; + worldCoords.z = (ndc.z * (near - far) - near - far) * 0.5; + worldCoords.w = 1.0; + + worldCoords = czm_inverseView * worldCoords; + return worldCoords.xyz; + } +}*/ + void main(void) { #ifdef VECTOR_TILE gl_FragColor = u_highlightColor; #else - gl_FragColor = v_color; + vec2 coords = gl_FragCoord.xy / czm_viewport.zw; + float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); + + if (gl_FragCoord.x / czm_viewport.z > 0.5) { + + vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); + vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); + vec4 worldCoord = czm_inverseView * eyeCoord; + + float height = length(worldCoord.xyz / worldCoord.w); + +// 6370000 + gl_FragColor = vec4(vec3((height - 6370000.0) / 10000.0), 1.0); + } else { + + gl_FragColor = vec4(vec3(depth), 1.0); + } #endif czm_writeDepthClampedToFarPlane(); } From 4fe0397a649379fb4549bf7f96b3a16b3aa60c66 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 20 Feb 2018 14:16:05 -0500 Subject: [PATCH 02/40] texcoords from naive inverse trig --- Source/DataSources/PolygonGeometryUpdater.js | 4 +- Source/Scene/ClassificationPrimitive.js | 2 + Source/Scene/GroundPrimitive.js | 42 ++++++++++--- Source/Shaders/ShadowVolumeFS.glsl | 65 +++++++------------- 4 files changed, 61 insertions(+), 52 deletions(-) diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index c0ab0c89817..a61b4241475 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -116,6 +116,7 @@ define([ this._shadowsProperty = undefined; this._distanceDisplayConditionProperty = undefined; this._onTerrain = false; + this._isColorMaterial = false; this._options = new GeometryOptions(entity); this._onEntityPropertyChanged(entity, 'polygon', entity.polygon, undefined); } @@ -503,6 +504,7 @@ define([ var material = defaultValue(polygon.material, defaultMaterial); var isColorMaterial = material instanceof ColorMaterialProperty; + this._isColorMaterial = isColorMaterial; this._materialProperty = material; this._fillProperty = defaultValue(fillProperty, defaultFill); this._showProperty = defaultValue(show, defaultShow); @@ -516,7 +518,7 @@ define([ var granularity = polygon.granularity; var stRotation = polygon.stRotation; var outlineWidth = polygon.outlineWidth; - var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) && isColorMaterial && + var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) && isColorMaterial && // TODO !perPositionHeightEnabled && GroundPrimitive.isSupported(this._scene); if (outlineEnabled && onTerrain) { diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index eba76b90f59..b53fb5ba5a9 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -6,6 +6,7 @@ define([ '../Core/destroyObject', '../Core/DeveloperError', '../Core/GeometryInstance', + '../Core/Rectangle', '../Core/isArray', '../Renderer/DrawCommand', '../Renderer/Pass', @@ -32,6 +33,7 @@ define([ destroyObject, DeveloperError, GeometryInstance, + Rectangle, isArray, DrawCommand, Pass, diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index c8a68f04082..002056d4300 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -3,6 +3,7 @@ define([ '../Core/buildModuleUrl', '../Core/Cartesian2', '../Core/Cartesian3', + '../Core/Cartesian4', '../Core/Cartographic', '../Core/defaultValue', '../Core/defined', @@ -26,6 +27,7 @@ define([ buildModuleUrl, Cartesian2, Cartesian3, + Cartesian4, Cartographic, defaultValue, defined, @@ -46,12 +48,6 @@ define([ SceneMode) { 'use strict'; - var GroundPrimitiveUniformMap = { - u_globeMinimumAltitude: function() { - return 55000.0; - } - }; - /** * A ground primitive represents geometry draped over the terrain in the {@link Scene}. The geometry must be from a single {@link GeometryInstance}. * Batching multiple geometries is not yet supported. @@ -213,6 +209,18 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; + var sphericalExtents = new Cartesian4(); + var uniformMap = { + u_globeMinimumAltitude: function() { + return 55000.0; + }, + u_sphericalExtents: function() { + return sphericalExtents; + } + }; + this._sphericalExtents = sphericalExtents; + this._uniformMap = uniformMap; + var that = this; this._primitiveOptions = { geometryInstances : undefined, @@ -226,7 +234,7 @@ define([ _updateAndQueueCommandsFunction : undefined, _pickPrimitive : that, _extruded : true, - _uniformMap : GroundPrimitiveUniformMap + _uniformMap : uniformMap }; } @@ -767,6 +775,26 @@ define([ updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); }; + // grab spherical extents. TODO: what to do if not all the geometryInstances are polygonHierarchies? TODO: does this even happen here? + var rectangleSphericalExtents = undefined; + for (i = 0; i < length; i++) { + var geometry = groundInstances[i].geometry; + if (defined(geometry._rectangle)) { + if (!defined(rectangleSphericalExtents)) { + rectangleSphericalExtents = Rectangle.clone(geometry._rectangle); + } else { + Rectangle.union(rectangleSphericalExtents, geometry._rectangle, rectangleSphericalExtents); + } + } + } + console.log(rectangleSphericalExtents); + var sphericalExtentsVec4 = this._sphericalExtents; + sphericalExtentsVec4.x = rectangleSphericalExtents.west; + sphericalExtentsVec4.y = rectangleSphericalExtents.south; + sphericalExtentsVec4.z = 1.0 / (rectangleSphericalExtents.east - rectangleSphericalExtents.west); + sphericalExtentsVec4.w = 1.0 / (rectangleSphericalExtents.north - rectangleSphericalExtents.south); + console.log(sphericalExtentsVec4); + this._primitive = new ClassificationPrimitive(primitiveOptions); this._primitive.readyPromise.then(function(primitive) { that._ready = true; diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index 941fb57df59..b8e80513008 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -6,42 +6,9 @@ uniform vec4 u_highlightColor; #else varying vec4 v_color; -#endif - -/* -vec3 drawingBufferToWgs84Coordinates(vec2 drawingBufferPosition, float depth) { - vec4 ndc; - ndc.x = (drawingBufferPosition.x - czm_viewport.x) / czm_viewport.z * 2.0 - 1.0; - ndc.y = (drawingBufferPosition.y - czm_viewport.y) / czm_viewport.w * 2.0 - 1.0; - ndc.z = (depth * 2.0) - 1.0; - ndc.w = 1.0; - - if (czm_inverseViewProjection != mat4(0.0)) { - vec4 worldCoords = czm_inverseViewProjection * ndc; - // Reverse perspective divide? - float w = 1.0 / worldCoords.w; - worldCoords.xyz * w; - return worldCoords.xyz; - } else { - // TODO: debug me toooo... - float top = czm_frustumPlanes.x; - float bottom = czm_frustumPlanes.y; - float left = czm_frustumPlanes.z; - float right = czm_frustumPlanes.w; - - float near = czm_currentFrustum.x; - float far = czm_currentFrustum.y; - - vec4 worldCoords; - worldCoords.x = (ndc.x * (right - left) + left + right) * 0.5; - worldCoords.y = (ndc.y * (top - bottom) + bottom + top) * 0.5; - worldCoords.z = (ndc.z * (near - far) - near - far) * 0.5; - worldCoords.w = 1.0; +uniform vec4 u_sphericalExtents; - worldCoords = czm_inverseView * worldCoords; - return worldCoords.xyz; - } -}*/ +#endif void main(void) { @@ -51,20 +18,30 @@ void main(void) vec2 coords = gl_FragCoord.xy / czm_viewport.zw; float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - if (gl_FragCoord.x / czm_viewport.z > 0.5) { + vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); + vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); + vec4 worldCoord4 = czm_inverseView * eyeCoord; + vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; - vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); - vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); - vec4 worldCoord = czm_inverseView * eyeCoord; + float height = length(worldCoord); + vec3 sphereNormal = normalize(worldCoord); - float height = length(worldCoord.xyz / worldCoord.w); + float longitude = asin(sphereNormal.z); // find a dress for the ball Sinderella + float latitude = atan(sphereNormal.y, sphereNormal.x); // the kitTans weep -// 6370000 - gl_FragColor = vec4(vec3((height - 6370000.0) / 10000.0), 1.0); - } else { + float u = (latitude - u_sphericalExtents.x) * u_sphericalExtents.z; + float v = (longitude - u_sphericalExtents.y) * u_sphericalExtents.w; - gl_FragColor = vec4(vec3(depth), 1.0); + float colorHeight = (height - 6370000.0) / 10000.0; + //gl_FragColor = vec4(vec2(u, v), 0.0, 0.5); + + // UV checkerboard + if (((mod(floor(u / 0.1), 2.0) == 1.0) && (mod(floor(v / 0.1), 2.0) == 0.0)) || ((mod(floor(u / 0.1), 2.0) == 0.0) && (mod(floor(v / 0.1), 2.0) == 1.0))) { + gl_FragColor = vec4(vec2(u, v), 0.0, 0.5); + } else { + gl_FragColor = vec4(0.0, vec2(u, v), 0.5); } + #endif czm_writeDepthClampedToFarPlane(); } From 956df6c5fa1b962d886f5996bfaeea88f005a36e Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 2 Mar 2018 09:35:34 -0500 Subject: [PATCH 03/40] using cg ref approx to compute spherical coordinates on CPU and GPU --- .../gallery/batchingGroundPrims.html | 152 +++++++++++++++++ ...hericalExtentsGeometryInstanceAttribute.js | 153 ++++++++++++++++++ .../StaticGroundGeometryColorBatch.js | 2 +- Source/Scene/ClassificationPrimitive.js | 6 +- Source/Scene/GroundPrimitive.js | 43 ++--- Source/Scene/Scene.js | 4 +- Source/Shaders/ShadowVolumeFS.glsl | 99 ++++++++++-- Source/Shaders/ShadowVolumeVS.glsl | 2 + Specs/Scene/GroundPrimitiveSpec.js | 2 +- 9 files changed, 416 insertions(+), 47 deletions(-) create mode 100644 Apps/Sandcastle/gallery/batchingGroundPrims.html create mode 100644 Source/Core/SphericalExtentsGeometryInstanceAttribute.js diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html new file mode 100644 index 00000000000..e49c4b2fab7 --- /dev/null +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -0,0 +1,152 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js new file mode 100644 index 00000000000..d23f908e785 --- /dev/null +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -0,0 +1,153 @@ +define([ + './Cartesian2', + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './defineProperties', + './Ellipsoid' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + ComponentDatatype, + defineProperties, + Ellipsoid) { + 'use strict'; + + function asinRef(x) { + var negate = x < 0.0 ? -1.0 : 1.0; + x = Math.abs(x); + var ret = -0.0187293; + ret *= x; + ret += 0.0742610; + ret *= x; + ret -= 0.2121144; + ret *= x; + ret += 1.5707288; + ret = 3.14159265358979 * 0.5 - Math.sqrt(1.0 - x) * ret; + return ret - 2.0 * negate * ret; + } + + function atan2Ref(y, x) { + var t0, t1, t2, t3, t4; + + t3 = Math.abs(x); + t1 = Math.abs(y); + t0 = Math.max(t3, t1); + t1 = Math.min(t3, t1); + t3 = 1.0 / t0; + t3 = t1 * t3; + + t4 = t3 * t3; + t0 = - 0.013480470; + t0 = t0 * t4 + 0.057477314; + t0 = t0 * t4 - 0.121239071; + t0 = t0 * t4 + 0.195635925; + t0 = t0 * t4 - 0.332994597; + t0 = t0 * t4 + 0.999995630; + t3 = t0 * t3; + + t3 = (Math.abs(y) > Math.abs(x)) ? 1.570796327 - t3 : t3; + t3 = (x < 0) ? 3.141592654 - t3 : t3; + t3 = (y < 0) ? -t3 : t3; + + return t3; + } + + var cartographicScratch = new Cartographic(); + var cartesian3Scratch = new Cartesian3(); + function latLongToSpherical(latitude, longitude, result) { + var carto = cartographicScratch; + carto.latitude = latitude; + carto.longitude = longitude; + carto.height = 0.0; + + var cartesian = Cartographic.toCartesian(carto, Ellipsoid.WGS84, cartesian3Scratch); + var sphereNormal = Cartesian3.normalize(cartesian, cartesian); + + var sphereLatitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella + var sphereLongitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep + result.x = sphereLatitude; + result.y = sphereLongitude; + + return result; + } + + var sphericalScratch = new Cartesian2(); + function SphericalExtentsGeometryInstanceAttribute(rectangle) { + // cartographic coords !== spherical because it's on an ellipsoid + console.log(rectangle); + + var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, sphericalScratch); + var south = southWestExtents.x; + var west = southWestExtents.y; + + var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, sphericalScratch); + var north = northEastExtents.x; + var east = northEastExtents.y; + + var longitudeRange = 1.0 / (east - west); + var latitudeRange = 1.0 / (north - south); + + + //console.log(rectangle); + //console.log('west: ' + west + ' east: ' + east + ' longitude range: ' + 1.0 / longitudeRange + ' latitude range: ' + 1.0 / latitudeRange) + + this.value = new Float32Array([west, south, longitudeRange, latitudeRange]); + } + + defineProperties(SphericalExtentsGeometryInstanceAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link SphericalExtentsGeometryInstanceAttribute#value}. + * + * @memberof SphericalExtentsGeometryInstanceAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link SphericalExtentsGeometryInstanceAttribute#value}. + * + * @memberof SphericalExtentsGeometryInstanceAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 4 + */ + componentsPerAttribute : { + get : function() { + return 4; + } + }, + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @memberof SphericalExtentsGeometryInstanceAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + return SphericalExtentsGeometryInstanceAttribute; +}); diff --git a/Source/DataSources/StaticGroundGeometryColorBatch.js b/Source/DataSources/StaticGroundGeometryColorBatch.js index e0c22eb2a10..b0235c2b33e 100644 --- a/Source/DataSources/StaticGroundGeometryColorBatch.js +++ b/Source/DataSources/StaticGroundGeometryColorBatch.js @@ -259,7 +259,7 @@ define([ var instance = updater.createFillGeometryInstance(time); var batches = this._batches; // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key - var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0]; + var batchKey = 1;//new Uint32Array(instance.attributes.color.value.buffer)[0]; var batch; if (batches.contains(batchKey)) { batch = batches.get(batchKey); diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index b53fb5ba5a9..2754e424919 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -858,9 +858,9 @@ define([ instance = instances[i]; var attributes = instance.attributes; if (!defined(attributes) || !defined(attributes.color)) { - throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); + //throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) { - throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); + //throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); } else if (!defined(color)) { color = attributes.color; } @@ -936,7 +936,7 @@ define([ this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); } - this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._primitive.debugShowBoundingVolume = true;//this.debugShowBoundingVolume; this._primitive.update(frameState); }; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 002056d4300..22db4e87f1c 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -17,6 +17,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/Rectangle', '../Core/Resource', + '../Core/SphericalExtentsGeometryInstanceAttribute', '../Renderer/Pass', '../ThirdParty/when', './ClassificationPrimitive', @@ -41,6 +42,7 @@ define([ OrientedBoundingBox, Rectangle, Resource, + SphericalExtentsGeometryInstanceAttribute, Pass, when, ClassificationPrimitive, @@ -209,16 +211,11 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; - var sphericalExtents = new Cartesian4(); var uniformMap = { u_globeMinimumAltitude: function() { return 55000.0; - }, - u_sphericalExtents: function() { - return sphericalExtents; } }; - this._sphericalExtents = sphericalExtents; this._uniformMap = uniformMap; var that = this; @@ -728,7 +725,7 @@ define([ geometry = instance.geometry; var instanceRectangle = getRectangle(frameState, geometry); if (!defined(rectangle)) { - rectangle = instanceRectangle; + rectangle = Rectangle.clone(instanceRectangle); } else if (defined(instanceRectangle)) { Rectangle.union(rectangle, instanceRectangle, rectangle); } @@ -758,10 +755,22 @@ define([ instance = instances[i]; geometry = instance.geometry; instanceType = geometry.constructor; + + // TODO: what to do if not all the geometryInstances are polygonHierarchies? TODO: does this even happen here? + var attributes = { + sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(geometry._rectangle) + }; + var instanceAttributes = instance.attributes; + for (var attributeKey in instanceAttributes) { + if (instanceAttributes.hasOwnProperty(attributeKey)) { + attributes[attributeKey] = instanceAttributes[attributeKey]; + } + } + groundInstances[i] = new GeometryInstance({ geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), getComputeMaximumHeightFunction(this)), - attributes : instance.attributes, + attributes : attributes, id : instance.id }); } @@ -775,26 +784,6 @@ define([ updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); }; - // grab spherical extents. TODO: what to do if not all the geometryInstances are polygonHierarchies? TODO: does this even happen here? - var rectangleSphericalExtents = undefined; - for (i = 0; i < length; i++) { - var geometry = groundInstances[i].geometry; - if (defined(geometry._rectangle)) { - if (!defined(rectangleSphericalExtents)) { - rectangleSphericalExtents = Rectangle.clone(geometry._rectangle); - } else { - Rectangle.union(rectangleSphericalExtents, geometry._rectangle, rectangleSphericalExtents); - } - } - } - console.log(rectangleSphericalExtents); - var sphericalExtentsVec4 = this._sphericalExtents; - sphericalExtentsVec4.x = rectangleSphericalExtents.west; - sphericalExtentsVec4.y = rectangleSphericalExtents.south; - sphericalExtentsVec4.z = 1.0 / (rectangleSphericalExtents.east - rectangleSphericalExtents.west); - sphericalExtentsVec4.w = 1.0 / (rectangleSphericalExtents.north - rectangleSphericalExtents.south); - console.log(sphericalExtentsVec4); - this._primitive = new ClassificationPrimitive(primitiveOptions); this._primitive.readyPromise.then(function(primitive) { that._ready = true; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index d2e375709d5..3fb0d50ffbd 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -624,10 +624,10 @@ define([ /** * Set to true to copy the depth texture after rendering the globe. Makes czm_globeDepthTexture valid. * @type {Boolean} - * @default false + * @default true * @private */ - this.copyGlobeDepth = false; + this.copyGlobeDepth = true; /** * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index b8e80513008..aca635fc219 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -6,10 +6,52 @@ uniform vec4 u_highlightColor; #else varying vec4 v_color; -uniform vec4 u_sphericalExtents; - +varying vec4 v_sphericalExtents; #endif +float rez = 0.1; + +float asinRef(float x) { + float negate = x < 0.0 ? -1.0 : 1.0; + x = abs(x); + float ret = -0.0187293; + ret *= x; + ret += 0.0742610; + ret *= x; + ret -= 0.2121144; + ret *= x; + ret += 1.5707288; + ret = 3.14159265358979 * 0.5 - sqrt(1.0 - x) * ret; + return ret - 2.0 * negate * ret; +} + +float atan2Ref(float y, float x) +{ + float t0, t1, t2, t3, t4; + + t3 = abs(x); + t1 = abs(y); + t0 = max(t3, t1); + t1 = min(t3, t1); + t3 = 1.0 / t0; + t3 = t1 * t3; + + t4 = t3 * t3; + t0 = - 0.013480470; + t0 = t0 * t4 + 0.057477314; + t0 = t0 * t4 - 0.121239071; + t0 = t0 * t4 + 0.195635925; + t0 = t0 * t4 - 0.332994597; + t0 = t0 * t4 + 0.999995630; + t3 = t0 * t3; + + t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; + t3 = (x < 0.0) ? 3.141592654 - t3 : t3; + t3 = (y < 0.0) ? -t3 : t3; + + return t3; +} + void main(void) { #ifdef VECTOR_TILE @@ -23,24 +65,55 @@ void main(void) vec4 worldCoord4 = czm_inverseView * eyeCoord; vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; - float height = length(worldCoord); + //float height = length(worldCoord); vec3 sphereNormal = normalize(worldCoord); - float longitude = asin(sphereNormal.z); // find a dress for the ball Sinderella - float latitude = atan(sphereNormal.y, sphereNormal.x); // the kitTans weep + float latitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella + float longitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep + + float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w; + float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z; - float u = (latitude - u_sphericalExtents.x) * u_sphericalExtents.z; - float v = (longitude - u_sphericalExtents.y) * u_sphericalExtents.w; + /* + // Snippet that made me realize spherical !== cartographic + float alpha = 1.0; + if (v < 0.5) { + alpha = 0.5; + } + if (u < 0.5) { + gl_FragColor = vec4(1.0, 0.0, 0.0, alpha); + } else { + gl_FragColor = vec4(0.0, 1.0, 0.0, alpha); + } + */ - float colorHeight = (height - 6370000.0) / 10000.0; - //gl_FragColor = vec4(vec2(u, v), 0.0, 0.5); - // UV checkerboard - if (((mod(floor(u / 0.1), 2.0) == 1.0) && (mod(floor(v / 0.1), 2.0) == 0.0)) || ((mod(floor(u / 0.1), 2.0) == 0.0) && (mod(floor(v / 0.1), 2.0) == 1.0))) { - gl_FragColor = vec4(vec2(u, v), 0.0, 0.5); + // snippet that I used to figure out that inverse trig functions on CPU and GPU have pretty noticeable differences + if (u < 0.0) { + gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); + } + else if (1.0 < u) { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + else if (v < 0.0) { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + else if (1.0 < v) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); } else { - gl_FragColor = vec4(0.0, vec2(u, v), 0.5); + // UV checkerboard + if (((mod(floor(u / rez), 2.0) == 1.0) && (mod(floor(v / rez), 2.0) == 0.0)) || ((mod(floor(u / rez), 2.0) == 0.0) && (mod(floor(v / rez), 2.0) == 1.0))) { + gl_FragColor = vec4(u, v, 0.0, 1.0); + } else { + gl_FragColor = v_color; + } } +/* + if (u < 0.0 || 1.0 < u || v < 0.0 || 1.0 < v) { + discard; + } else { + gl_FragColor = v_color; + }*/ #endif czm_writeDepthClampedToFarPlane(); diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 9d83f1a2958..bb7721936ed 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -18,6 +18,7 @@ uniform float u_globeMinimumAltitude; #ifndef VECTOR_TILE varying vec4 v_color; +varying vec4 v_sphericalExtents; #endif void main() @@ -26,6 +27,7 @@ void main() gl_Position = czm_depthClampFarPlane(u_modifiedModelViewProjection * vec4(position, 1.0)); #else v_color = color; + v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); vec4 position = czm_computePosition(); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index a17aab5a121..808a0830ecd 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -619,7 +619,7 @@ defineSuite([ attributes : { color : rectColorAttribute, distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) - } + } // Dan: attributes added here "automagically" show up in the shader in the batch table. Look at DistanceDisplayConditionGeometryInstanceAttribute }); primitive = new GroundPrimitive({ From 529b5ab55a4a6161e9fc5a1a1c71075cdf892681 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Sat, 3 Mar 2018 22:11:26 -0500 Subject: [PATCH 04/40] add separate shader for shadow volume final pass, use a cubic instead of asin --- .../gallery/batchingGroundPrims.html | 82 ++++++++++++-- ...hericalExtentsGeometryInstanceAttribute.js | 27 ++--- Source/Scene/ClassificationPrimitive.js | 20 +++- Source/Shaders/ShadowVolumeColorFS.glsl | 84 ++++++++++++++ Source/Shaders/ShadowVolumeFS.glsl | 103 +----------------- 5 files changed, 184 insertions(+), 132 deletions(-) create mode 100644 Source/Shaders/ShadowVolumeColorFS.glsl diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html index e49c4b2fab7..26bef6e8b20 100644 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -36,6 +36,13 @@ viewer.scene.globe.depthTestAgainstTerrain = true; viewer.scene.copyGlobeDepth = true; +/* +var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider({ + url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', + requestWaterMask : true, + requestVertexNormals : true +}); +viewer.terrainProvider = cesiumTerrainProviderMeshes;*/ function offsetPositions(positions, degreeOffset) { positions = scene.globe.ellipsoid.cartesianArrayToCartographicArray(positions); @@ -48,6 +55,46 @@ return scene.globe.ellipsoid.cartographicArrayToCartesianArray(positions); } +function asinRef(x) { + var negate = x < 0.0 ? -1.0 : 1.0; + x = Math.abs(x); + var ret = -0.0187293; + ret *= x; + ret += 0.0742610; + ret *= x; + ret -= 0.2121144; + ret *= x; + ret += 1.5707288; + ret = 3.14159265358979 * 0.5 - Math.sqrt(1.0 - x) * ret; + return ret - 2.0 * negate * ret; +} + +function atan2Ref(y, x) { + var t0, t1, t2, t3, t4; + + t3 = Math.abs(x); + t1 = Math.abs(y); + t0 = Math.max(t3, t1); + t1 = Math.min(t3, t1); + t3 = 1.0 / t0; + t3 = t1 * t3; + + t4 = t3 * t3; + t0 = - 0.013480470; + t0 = t0 * t4 + 0.057477314; + t0 = t0 * t4 - 0.121239071; + t0 = t0 * t4 + 0.195635925; + t0 = t0 * t4 - 0.332994597; + t0 = t0 * t4 + 0.999995630; + t3 = t0 * t3; + + t3 = (Math.abs(y) > Math.abs(x)) ? 1.570796327 - t3 : t3; + t3 = (x < 0) ? 3.141592654 - t3 : t3; + t3 = (y < 0) ? -t3 : t3; + + return t3; +} + var cartographicScratch = new Cesium.Cartographic(); var cartesian3Scratch = new Cesium.Cartesian3(); function latLongToSpherical(latitude, longitude, result) { @@ -59,8 +106,8 @@ var cartesian = Cesium.Cartographic.toCartesian(carto, Cesium.Ellipsoid.WGS84, cartesian3Scratch); var sphereNormal = Cesium.Cartesian3.normalize(cartesian, cartesian); - var sphereLatitude = Math.asin(sphereNormal.z); // find a dress for the ball Sinderella - var sphereLongitude = Math.atan2(sphereNormal.y, sphereNormal.x); // the kitTans weep + var sphereLatitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella + var sphereLongitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep result.x = Cesium.Math.toDegrees(sphereLongitude); result.y = Cesium.Math.toDegrees(sphereLatitude); @@ -82,7 +129,7 @@ material : Cesium.Color.RED.withAlpha(0.5) } });*/ - +/* var redPolygon2 = viewer.entities.add({ name : 'Red polygon on surface', polygon : { @@ -97,15 +144,31 @@ hierarchy : offsetPositions(positions, 0.01), material : Cesium.Color.BLUE.withAlpha(0.5) } -}); -/* +});*/ + var rectangle = viewer.entities.add({ name : 'Red polygon on surface', rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(-122.1956, 45.1914, -122.1856, 45.2014), + coordinates : Cesium.Rectangle.fromDegrees(-0.01, -0.01, 0.01, 0.01), material : Cesium.Color.GREEN.withAlpha(0.5) } -});*/ +}); + +var rectangle1 = viewer.entities.add({ + name : 'Red polygon on surface', + rectangle : { + coordinates : Cesium.Rectangle.fromDegrees(0.011, 0.011, 0.031, 0.031), + material : Cesium.Color.GREEN.withAlpha(0.5) + } +}); + +var rectangle2 = viewer.entities.add({ + name : 'Red polygon on surface', + rectangle : { + coordinates : Cesium.Rectangle.fromDegrees(-0.031, -0.031, -0.011, -0.011), + material : Cesium.Color.GREEN.withAlpha(0.5) + } +}); // click the globe to see the cartographic position var leftHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); @@ -126,11 +189,14 @@ var lat = Cesium.Math.toDegrees(cartographic.latitude); var long = Cesium.Math.toDegrees(cartographic.longitude); + var normalized = Cesium.Cartesian3.normalize(cartesian, cartesian); + console.log(normalized); + viewer.entities.removeAll(); viewer.entities.add({ name : lat + ' ' + long, rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(long - 0.01, lat - 0.01, long + 0.01, lat + 0.01), + coordinates : Cesium.Rectangle.fromDegrees(long - 10.01, lat - 10.01, long + 10.01, lat + 10.01), material : Cesium.Color.GREEN.withAlpha(0.5) } }); diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js index d23f908e785..852c638efa5 100644 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -14,22 +14,13 @@ define([ Ellipsoid) { 'use strict'; - function asinRef(x) { - var negate = x < 0.0 ? -1.0 : 1.0; - x = Math.abs(x); - var ret = -0.0187293; - ret *= x; - ret += 0.0742610; - ret *= x; - ret -= 0.2121144; - ret *= x; - ret += 1.5707288; - ret = 3.14159265358979 * 0.5 - Math.sqrt(1.0 - x) * ret; - return ret - 2.0 * negate * ret; + function completelyFakeAsin(x) + { + return (x * x * x + x) * 0.78539816339; } function atan2Ref(y, x) { - var t0, t1, t2, t3, t4; + var t0, t1, t3, t4; t3 = Math.abs(x); t1 = Math.abs(y); @@ -65,7 +56,7 @@ define([ var cartesian = Cartographic.toCartesian(carto, Ellipsoid.WGS84, cartesian3Scratch); var sphereNormal = Cartesian3.normalize(cartesian, cartesian); - var sphereLatitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella + var sphereLatitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella var sphereLongitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep result.x = sphereLatitude; result.y = sphereLongitude; @@ -75,9 +66,7 @@ define([ var sphericalScratch = new Cartesian2(); function SphericalExtentsGeometryInstanceAttribute(rectangle) { - // cartographic coords !== spherical because it's on an ellipsoid - console.log(rectangle); - + // rectangle cartographic coords !== spherical because it's on an ellipsoid var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, sphericalScratch); var south = southWestExtents.x; var west = southWestExtents.y; @@ -89,9 +78,7 @@ define([ var longitudeRange = 1.0 / (east - west); var latitudeRange = 1.0 / (north - south); - - //console.log(rectangle); - //console.log('west: ' + west + ' east: ' + east + ' longitude range: ' + 1.0 / longitudeRange + ' latitude range: ' + 1.0 / latitudeRange) + console.log('north: ' + north + ' south: ' + south + ' east: ' + east + ' west: ' + west); this.value = new Float32Array([west, south, longitudeRange, latitudeRange]); } diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 2754e424919..11e362098e0 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -13,6 +13,7 @@ define([ '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', + '../Shaders/ShadowVolumeColorFS', '../Shaders/ShadowVolumeFS', '../Shaders/ShadowVolumeVS', '../ThirdParty/when', @@ -40,6 +41,7 @@ define([ RenderState, ShaderProgram, ShaderSource, + ShadowVolumeColorFS, ShadowVolumeFS, ShadowVolumeVS, when, @@ -170,6 +172,7 @@ define([ this._sp = undefined; this._spStencil = undefined; this._spPick = undefined; + this._spColor = undefined; this._rsStencilPreloadPass = undefined; this._rsStencilDepthPass = undefined; @@ -594,6 +597,18 @@ define([ fragmentShaderSource : fsSource, attributeLocations : attributeLocations }); + + var fsColorSource = new ShaderSource({ + sources : [ShadowVolumeColorFS] + }); + + classificationPrimitive._spColor = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spColor, + vertexShaderSource : vsSource, + fragmentShaderSource : fsColorSource, + attributeLocations : attributeLocations + }); } function createColorCommands(classificationPrimitive, colorCommands) { @@ -648,7 +663,7 @@ define([ command.vertexArray = vertexArray; command.renderState = classificationPrimitive._rsColorPass; - command.shaderProgram = classificationPrimitive._sp; + command.shaderProgram = classificationPrimitive._spColor; command.uniformMap = uniformMap; } @@ -936,7 +951,7 @@ define([ this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); } - this._primitive.debugShowBoundingVolume = true;//this.debugShowBoundingVolume; + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); }; @@ -999,6 +1014,7 @@ define([ this._primitive = this._primitive && this._primitive.destroy(); this._sp = this._sp && this._sp.destroy(); this._spPick = this._spPick && this._spPick.destroy(); + this._spColor = this._spColor && this._spColor.destroy(); return destroyObject(this); }; diff --git a/Source/Shaders/ShadowVolumeColorFS.glsl b/Source/Shaders/ShadowVolumeColorFS.glsl new file mode 100644 index 00000000000..344aad27c39 --- /dev/null +++ b/Source/Shaders/ShadowVolumeColorFS.glsl @@ -0,0 +1,84 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +#ifdef VECTOR_TILE +uniform vec4 u_highlightColor; +#else +varying vec4 v_color; +varying vec4 v_sphericalExtents; +#endif + +float rez = 0.1; + +// http://developer.download.nvidia.com/cg/atan2.html +// Using this instead of identities + approximations of atan, +// because atan approximations usually only work between -1 and 1. +float atan2Ref(float y, float x) +{ + float t0, t1, t3, t4; + + t3 = abs(x); + t1 = abs(y); + t0 = max(t3, t1); + t1 = min(t3, t1); + t3 = 1.0 / t0; + t3 = t1 * t3; + + t4 = t3 * t3; + t0 = - 0.013480470; + t0 = t0 * t4 + 0.057477314; + t0 = t0 * t4 - 0.121239071; + t0 = t0 * t4 + 0.195635925; + t0 = t0 * t4 - 0.332994597; + t0 = t0 * t4 + 0.999995630; + t3 = t0 * t3; + + t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; + t3 = (x < 0.0) ? 3.141592654 - t3 : t3; + t3 = (y < 0.0) ? -t3 : t3; + + return t3; +} + +// kind of inaccurate, but no sucky discontinuities! +float completelyFakeAsin(float x) +{ + return (x * x * x + x) * 0.78539816339; +} + +void main(void) +{ +#ifdef VECTOR_TILE + gl_FragColor = u_highlightColor; +#else + vec2 coords = gl_FragCoord.xy / czm_viewport.zw; + float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); + + vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); + vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); + vec4 worldCoord4 = czm_inverseView * eyeCoord; + vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; + + vec3 sphereNormal = normalize(worldCoord); + + float latitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella + float longitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep + + float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w; + float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z; + + if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { + discard; + } else { + // UV checkerboard + if (((mod(floor(u / rez), 2.0) == 1.0) && (mod(floor(v / rez), 2.0) == 0.0)) || ((mod(floor(u / rez), 2.0) == 0.0) && (mod(floor(v / rez), 2.0) == 1.0))) { + gl_FragColor = vec4(u, v, 0.0, 1.0); + } else { + gl_FragColor = v_color; + } + } + +#endif + czm_writeDepthClampedToFarPlane(); +} diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index aca635fc219..51e951b07b9 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -6,115 +6,14 @@ uniform vec4 u_highlightColor; #else varying vec4 v_color; -varying vec4 v_sphericalExtents; #endif -float rez = 0.1; - -float asinRef(float x) { - float negate = x < 0.0 ? -1.0 : 1.0; - x = abs(x); - float ret = -0.0187293; - ret *= x; - ret += 0.0742610; - ret *= x; - ret -= 0.2121144; - ret *= x; - ret += 1.5707288; - ret = 3.14159265358979 * 0.5 - sqrt(1.0 - x) * ret; - return ret - 2.0 * negate * ret; -} - -float atan2Ref(float y, float x) -{ - float t0, t1, t2, t3, t4; - - t3 = abs(x); - t1 = abs(y); - t0 = max(t3, t1); - t1 = min(t3, t1); - t3 = 1.0 / t0; - t3 = t1 * t3; - - t4 = t3 * t3; - t0 = - 0.013480470; - t0 = t0 * t4 + 0.057477314; - t0 = t0 * t4 - 0.121239071; - t0 = t0 * t4 + 0.195635925; - t0 = t0 * t4 - 0.332994597; - t0 = t0 * t4 + 0.999995630; - t3 = t0 * t3; - - t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; - t3 = (x < 0.0) ? 3.141592654 - t3 : t3; - t3 = (y < 0.0) ? -t3 : t3; - - return t3; -} - void main(void) { #ifdef VECTOR_TILE gl_FragColor = u_highlightColor; #else - vec2 coords = gl_FragCoord.xy / czm_viewport.zw; - float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - - vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); - vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); - vec4 worldCoord4 = czm_inverseView * eyeCoord; - vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; - - //float height = length(worldCoord); - vec3 sphereNormal = normalize(worldCoord); - - float latitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella - float longitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep - - float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w; - float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z; - - /* - // Snippet that made me realize spherical !== cartographic - float alpha = 1.0; - if (v < 0.5) { - alpha = 0.5; - } - if (u < 0.5) { - gl_FragColor = vec4(1.0, 0.0, 0.0, alpha); - } else { - gl_FragColor = vec4(0.0, 1.0, 0.0, alpha); - } - */ - - - // snippet that I used to figure out that inverse trig functions on CPU and GPU have pretty noticeable differences - if (u < 0.0) { - gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); - } - else if (1.0 < u) { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); - } - else if (v < 0.0) { - gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); - } - else if (1.0 < v) { - gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); - } else { - // UV checkerboard - if (((mod(floor(u / rez), 2.0) == 1.0) && (mod(floor(v / rez), 2.0) == 0.0)) || ((mod(floor(u / rez), 2.0) == 0.0) && (mod(floor(v / rez), 2.0) == 1.0))) { - gl_FragColor = vec4(u, v, 0.0, 1.0); - } else { - gl_FragColor = v_color; - } - } -/* - if (u < 0.0 || 1.0 < u || v < 0.0 || 1.0 < v) { - discard; - } else { - gl_FragColor = v_color; - }*/ - + gl_FragColor = v_color; #endif czm_writeDepthClampedToFarPlane(); } From 3e5c7243ba332f804521252c5a0a5388bebdeb75 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 20 Mar 2018 18:05:35 -0400 Subject: [PATCH 05/40] WIP material support for ground primitives w/normals from depth tex --- Apps/Sandcastle/gallery/Hello World.html | 70 ++ .../gallery/batchingGroundPrims.html | 184 +++--- ...hericalExtentsGeometryInstanceAttribute.js | 14 +- Source/DataSources/CorridorGeometryUpdater.js | 4 +- Source/DataSources/EllipseGeometryUpdater.js | 4 +- Source/DataSources/GeometryVisualizer.js | 14 +- Source/DataSources/PolygonGeometryUpdater.js | 4 +- .../DataSources/RectangleGeometryUpdater.js | 4 +- .../StaticGroundGeometryColorBatch.js | 613 +++++++++--------- .../StaticGroundGeometryPerMaterialBatch.js | 386 +++++++++++ Source/Scene/ClassificationPrimitive.js | 88 ++- Source/Scene/GroundPrimitive.js | 53 +- .../EllipsoidSurfaceAppearanceFS.glsl | 15 +- .../Materials/CheckerboardMaterial.glsl | 10 +- Source/Shaders/ShadowVolumeColorFS.glsl | 69 +- Source/Shaders/ShadowVolumeFS.glsl | 6 +- Source/Shaders/ShadowVolumeVS.glsl | 13 +- 17 files changed, 1065 insertions(+), 486 deletions(-) create mode 100644 Source/DataSources/StaticGroundGeometryPerMaterialBatch.js diff --git a/Apps/Sandcastle/gallery/Hello World.html b/Apps/Sandcastle/gallery/Hello World.html index 640e8e31770..e22601b8792 100644 --- a/Apps/Sandcastle/gallery/Hello World.html +++ b/Apps/Sandcastle/gallery/Hello World.html @@ -28,6 +28,76 @@ 'use strict'; //Sandcastle_Begin var viewer = new Cesium.Viewer('cesiumContainer'); + +var instance = new Cesium.GeometryInstance({ + geometry : new Cesium.EllipseGeometry({ + center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0), + semiMinorAxis : 500000.0, + semiMajorAxis : 1000000.0, + rotation : Cesium.Math.PI_OVER_FOUR, + vertexFormat : Cesium.VertexFormat.POSITION_AND_ST + }), + id : 'object returned when this instance is picked and to get/set per-instance attributes' +}); + +var checkerboardMaterial = Cesium.Material.fromType('Checkerboard'); + +var ellipsoidSurfaceAppearance1 = new Cesium.EllipsoidSurfaceAppearance({ + material : checkerboardMaterial +}); + +var stripeMaterial = Cesium.Material.fromType('Stripe'); + +var ellipsoidSurfaceAppearance2 = new Cesium.EllipsoidSurfaceAppearance({ + material : stripeMaterial +}); + +var perInstanceColorAppearance = new Cesium.PerInstanceColorAppearance({ + flat : true +}); + +var elPrimitive = viewer.scene.primitives.add(new Cesium.GroundPrimitive({ + geometryInstances : instance, + appearance : ellipsoidSurfaceAppearance1 +})); +/* +var greenRectangle = viewer.entities.add({ + name : 'Green translucent, rotated, and extruded rectangle at height with outline', + rectangle : { + coordinates : Cesium.Rectangle.fromRadians(-1.8582078985933261, 0.23654029197299448, -1.6230525464414889, 0.4570541830085829), + material : Cesium.Color.GREEN.withAlpha(0.5), + height : 0, + outline : true, // height must be set for outline to display + outlineColor : Cesium.Color.BLACK + } +}); +*/ +Sandcastle.addToolbarButton('switch appearances to stripe material', function() { + ellipsoidSurfaceAppearance1.material = stripeMaterial; + ellipsoidSurfaceAppearance2.material = stripeMaterial; +}); + +Sandcastle.addToolbarButton('switch appearances to check material', function() { + ellipsoidSurfaceAppearance1.material = checkerboardMaterial; + ellipsoidSurfaceAppearance2.material = checkerboardMaterial; +}); + +Sandcastle.addToolbarButton('switch to appearance 1', function() { + elPrimitive.appearance = ellipsoidSurfaceAppearance1; +}); + +Sandcastle.addToolbarButton('switch to appearance 2', function() { + elPrimitive.appearance = ellipsoidSurfaceAppearance2; +}); + +Sandcastle.addToolbarButton('switch to color appearance', function() { + elPrimitive.appearance = perInstanceColorAppearance; +}); + +Sandcastle.addToolbarButton('switch to undefined appearance', function() { + elPrimitive.appearance = undefined; +}); + //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html index 26bef6e8b20..c1c711fa099 100644 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -29,20 +29,24 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin + +var worldTerrain = Cesium.createWorldTerrain({ + requestWaterMask: true, + requestVertexNormals: true +}); + var viewer = new Cesium.Viewer('cesiumContainer', { - selectionIndicator: false + selectionIndicator: false, + terrainProvider: worldTerrain }); +/* +viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin); +var inspectorViewModel = viewer.cesium3DTilesInspector.viewModel; +*/ var scene = viewer.scene; viewer.scene.globe.depthTestAgainstTerrain = true; viewer.scene.copyGlobeDepth = true; -/* -var cesiumTerrainProviderMeshes = new Cesium.CesiumTerrainProvider({ - url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', - requestWaterMask : true, - requestVertexNormals : true -}); -viewer.terrainProvider = cesiumTerrainProviderMeshes;*/ function offsetPositions(positions, degreeOffset) { positions = scene.globe.ellipsoid.cartesianArrayToCartographicArray(positions); @@ -55,71 +59,35 @@ return scene.globe.ellipsoid.cartographicArrayToCartesianArray(positions); } -function asinRef(x) { - var negate = x < 0.0 ? -1.0 : 1.0; - x = Math.abs(x); - var ret = -0.0187293; - ret *= x; - ret += 0.0742610; - ret *= x; - ret -= 0.2121144; - ret *= x; - ret += 1.5707288; - ret = 3.14159265358979 * 0.5 - Math.sqrt(1.0 - x) * ret; - return ret - 2.0 * negate * ret; -} - -function atan2Ref(y, x) { - var t0, t1, t2, t3, t4; - - t3 = Math.abs(x); - t1 = Math.abs(y); - t0 = Math.max(t3, t1); - t1 = Math.min(t3, t1); - t3 = 1.0 / t0; - t3 = t1 * t3; - - t4 = t3 * t3; - t0 = - 0.013480470; - t0 = t0 * t4 + 0.057477314; - t0 = t0 * t4 - 0.121239071; - t0 = t0 * t4 + 0.195635925; - t0 = t0 * t4 - 0.332994597; - t0 = t0 * t4 + 0.999995630; - t3 = t0 * t3; - - t3 = (Math.abs(y) > Math.abs(x)) ? 1.570796327 - t3 : t3; - t3 = (x < 0) ? 3.141592654 - t3 : t3; - t3 = (y < 0) ? -t3 : t3; - - return t3; -} - -var cartographicScratch = new Cesium.Cartographic(); -var cartesian3Scratch = new Cesium.Cartesian3(); -function latLongToSpherical(latitude, longitude, result) { - var carto = cartographicScratch; - carto.latitude = latitude; - carto.longitude = longitude; - carto.height = 0.0; - - var cartesian = Cesium.Cartographic.toCartesian(carto, Cesium.Ellipsoid.WGS84, cartesian3Scratch); - var sphereNormal = Cesium.Cartesian3.normalize(cartesian, cartesian); - - var sphereLatitude = asinRef(sphereNormal.z); // find a dress for the ball Sinderella - var sphereLongitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep - result.x = Cesium.Math.toDegrees(sphereLongitude); - result.y = Cesium.Math.toDegrees(sphereLatitude); - - return result; -} - - var positions = [new Cesium.Cartesian3(-2358138.847340281, -3744072.459541374, 4581158.5714175375), new Cesium.Cartesian3(-2357231.4925370603, -3745103.7886602185, 4580702.9757762635), new Cesium.Cartesian3(-2355912.902205431, -3744249.029778454, 4582402.154378103), new Cesium.Cartesian3(-2357208.0209552636, -3743553.4420488174, 4581961.863286629)]; +var concavePositions = [ + new Cesium.Cartesian3(-2353381.4891308164, -3747386.1222378365, 4577999.291515961), + new Cesium.Cartesian3(-2359513.937204245, -3743087.2343810294, 4578357.188560644), + new Cesium.Cartesian3(-2356102.0286082155, -3739921.552293276, 4582670.218770547), + new Cesium.Cartesian3(-2353889.0353209395, -3741183.2274413602, 4582776.909071608), + new Cesium.Cartesian3(-2355072.390487758, -3742865.615615464, 4580808.044684757), + new Cesium.Cartesian3(-2356109.6661414686, -3741994.0607898533, 4580985.489703348), + new Cesium.Cartesian3(-2357041.8328847606, -3743225.9693035223, 4579509.2148039425), + new Cesium.Cartesian3(-2354586.752280607, -3744890.9511893727, 4579411.591389144), + new Cesium.Cartesian3(-2353213.0268325945, -3743712.1202877173, 4581070.08828045), + new Cesium.Cartesian3(-2353637.930711704, -3743402.9513476435, 4581104.219550749), + new Cesium.Cartesian3(-2352875.095159641, -3742564.819171856, 4582173.540953957), + new Cesium.Cartesian3(-2350669.646050987, -3743751.6823160048, 4582334.8406995395) +]; + +// concave polygon +var redPolygon1 = viewer.entities.add({ + name : 'concave polygon on surface', + polygon : { + hierarchy : offsetPositions(concavePositions, 0.0), + material : '../images/Cesium_Logo_Color.jpg' + } +}); + // polygons with non-overlapping extents seem to be batchable without problems /* var redPolygon1 = viewer.entities.add({ @@ -128,8 +96,8 @@ hierarchy : offsetPositions(positions, 0.0), material : Cesium.Color.RED.withAlpha(0.5) } -});*/ -/* +}); + var redPolygon2 = viewer.entities.add({ name : 'Red polygon on surface', polygon : { @@ -144,43 +112,62 @@ hierarchy : offsetPositions(positions, 0.01), material : Cesium.Color.BLUE.withAlpha(0.5) } -});*/ +}); -var rectangle = viewer.entities.add({ +var redPolygon4 = viewer.entities.add({ name : 'Red polygon on surface', - rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(-0.01, -0.01, 0.01, 0.01), - material : Cesium.Color.GREEN.withAlpha(0.5) + polygon : { + hierarchy : offsetPositions(positions, 0.02), + material : '../images/Cesium_Logo_Color.jpg' } }); -var rectangle1 = viewer.entities.add({ +var redPolygon5 = viewer.entities.add({ name : 'Red polygon on surface', + polygon : { + hierarchy : offsetPositions(positions, 0.03), + material : + new Cesium.CheckerboardMaterialProperty({ + evenColor : Cesium.Color.WHITE, + oddColor : Cesium.Color.BLACK, + repeat : new Cesium.Cartesian2(4, 4) + }) + } +});*/ + +// nearly overlapping rectangles over mt. st. helens +/* +var latitude = 46.1922; +var longitude = -122.1934; + +viewer.entities.add({ rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(0.011, 0.011, 0.031, 0.031), - material : Cesium.Color.GREEN.withAlpha(0.5) + coordinates : Cesium.Rectangle.fromDegrees(longitude + 0.001, latitude + 0.001, longitude + 0.2, latitude + 0.2), + material : Cesium.Color.BLUE.withAlpha(0.5) } }); -var rectangle2 = viewer.entities.add({ - name : 'Red polygon on surface', +viewer.entities.add({ rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(-0.031, -0.031, -0.011, -0.011), + coordinates : Cesium.Rectangle.fromDegrees(longitude - 0.2, latitude + 0.001, longitude, latitude + 0.2), material : Cesium.Color.GREEN.withAlpha(0.5) } }); -// click the globe to see the cartographic position -var leftHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); -leftHandler.setInputAction(function(movement) { - var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); - if (cartesian) { - var cartographic = Cesium.Cartographic.fromCartesian(cartesian); - console.log(cartographic); - console.log(latLongToSpherical(cartographic.latitude, cartographic.longitude, new Cesium.Cartesian2())); +viewer.entities.add({ + rectangle : { + coordinates : Cesium.Rectangle.fromDegrees(longitude - 0.2, latitude - 0.2, longitude, latitude), + material : Cesium.Color.RED.withAlpha(0.5) } -}, Cesium.ScreenSpaceEventType.LEFT_CLICK); +}); +viewer.entities.add({ + rectangle : { + coordinates : Cesium.Rectangle.fromDegrees(longitude + 0.001, latitude - 0.2, longitude + 0.2, latitude), + material : Cesium.Color.YELLOW.withAlpha(0.5) + } +}); +*/ var rightHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); rightHandler.setInputAction(function(movement) { var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); @@ -196,13 +183,26 @@ viewer.entities.add({ name : lat + ' ' + long, rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(long - 10.01, lat - 10.01, long + 10.01, lat + 10.01), - material : Cesium.Color.GREEN.withAlpha(0.5) + coordinates : Cesium.Rectangle.fromDegrees(long - 0.2, lat - 0.1, long + 0.2, lat + 0.1), + material : + new Cesium.CheckerboardMaterialProperty({ + evenColor : Cesium.Color.ORANGE, + oddColor : Cesium.Color.YELLOW, + repeat : new Cesium.Cartesian2(14, 14) + }) } }); } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); +var rightHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); +rightHandler.setInputAction(function(movement) { + var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); + if (cartesian) { + console.log('new Cesium.Cartesian3(' + cartesian.x + ', ' + cartesian.y + ', ' + cartesian.z + '),'); + } +}, Cesium.ScreenSpaceEventType.LEFT_CLICK); + viewer.zoomTo(viewer.entities); //Sandcastle_End diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js index 852c638efa5..b450eefcfb0 100644 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -68,18 +68,20 @@ define([ function SphericalExtentsGeometryInstanceAttribute(rectangle) { // rectangle cartographic coords !== spherical because it's on an ellipsoid var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, sphericalScratch); - var south = southWestExtents.x; - var west = southWestExtents.y; + + // Slightly pad extents to avoid floating point error when fragment culling at edges. + // TODO: what's the best value for this? + // TODO: should we undo this in the shader? + var south = southWestExtents.x - 0.00001; + var west = southWestExtents.y - 0.00001; var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, sphericalScratch); - var north = northEastExtents.x; - var east = northEastExtents.y; + var north = northEastExtents.x + 0.00001; + var east = northEastExtents.y + 0.00001; var longitudeRange = 1.0 / (east - west); var latitudeRange = 1.0 / (north - south); - console.log('north: ' + north + ' south: ' + south + ' east: ' + east + ' west: ' + west); - this.value = new Float32Array([west, south, longitudeRange, latitudeRange]); } diff --git a/Source/DataSources/CorridorGeometryUpdater.js b/Source/DataSources/CorridorGeometryUpdater.js index 338a33baecc..dcf281359a6 100644 --- a/Source/DataSources/CorridorGeometryUpdater.js +++ b/Source/DataSources/CorridorGeometryUpdater.js @@ -165,10 +165,10 @@ define([ }; CorridorGeometryUpdater.prototype._isOnTerrain = function(entity, corridor) { - var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; + //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; return this._fillEnabled && !defined(corridor.height) && !defined(corridor.extrudedHeight) && - isColorMaterial && GroundPrimitive.isSupported(this._scene); + GroundPrimitive.isSupported(this._scene);// && isColorMaterial; }; CorridorGeometryUpdater.prototype._getIsClosed = function(options) { diff --git a/Source/DataSources/EllipseGeometryUpdater.js b/Source/DataSources/EllipseGeometryUpdater.js index 45c1eec20ee..ec3f535408d 100644 --- a/Source/DataSources/EllipseGeometryUpdater.js +++ b/Source/DataSources/EllipseGeometryUpdater.js @@ -172,9 +172,9 @@ define([ }; EllipseGeometryUpdater.prototype._isOnTerrain = function(entity, ellipse) { - var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; + //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; - return this._fillEnabled && !defined(ellipse.height) && !defined(ellipse.extrudedHeight) && isColorMaterial && GroundPrimitive.isSupported(this._scene); + return this._fillEnabled && !defined(ellipse.height) && !defined(ellipse.extrudedHeight) && GroundPrimitive.isSupported(this._scene); //&& isColorMaterial; }; EllipseGeometryUpdater.prototype._isDynamic = function(entity, ellipse) { diff --git a/Source/DataSources/GeometryVisualizer.js b/Source/DataSources/GeometryVisualizer.js index abe85d68011..1e31053e7f7 100644 --- a/Source/DataSources/GeometryVisualizer.js +++ b/Source/DataSources/GeometryVisualizer.js @@ -26,6 +26,7 @@ define([ './StaticGeometryColorBatch', './StaticGeometryPerMaterialBatch', './StaticGroundGeometryColorBatch', + './StaticGroundGeometryPerMaterialBatch', './StaticOutlineGeometryBatch', './WallGeometryUpdater' ], function( @@ -56,6 +57,7 @@ define([ StaticGeometryColorBatch, StaticGeometryPerMaterialBatch, StaticGroundGeometryColorBatch, + StaticGroundGeometryPerMaterialBatch, StaticOutlineGeometryBatch, WallGeometryUpdater) { 'use strict'; @@ -153,14 +155,16 @@ define([ var numberOfClassificationTypes = ClassificationType.NUMBER_OF_CLASSIFICATION_TYPES; this._groundColorBatches = new Array(numberOfClassificationTypes); + this._groundMaterialBatches = new Array(numberOfClassificationTypes); // TODO: why is this? for (i = 0; i < numberOfClassificationTypes; ++i) { - this._groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, i); + this._groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, PerInstanceColorAppearance, i); + this._groundMaterialBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance, i); } this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); - this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatches, this._dynamicBatch); + this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatches, this._groundMaterialBatches, this._dynamicBatch); this._subscriptions = new AssociativeArray(); this._updaterSets = new AssociativeArray(); @@ -372,7 +376,11 @@ define([ if (updater.fillEnabled) { if (updater.onTerrain) { var classificationType = updater.classificationTypeProperty.getValue(time); - this._groundColorBatches[classificationType].add(time, updater); + if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { + this._groundColorBatches[classificationType].add(time, updater); + } else { + this._groundMaterialBatches[classificationType].add(time, updater); + } } else if (updater.isClosed) { if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { this._closedColorBatches[shadows].add(time, updater); diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index 293d463812d..0f3067d3ece 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -173,10 +173,10 @@ define([ }; PolygonGeometryUpdater.prototype._isOnTerrain = function(entity, polygon) { - var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; + //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; var perPositionHeightProperty = polygon.perPositionHeight; var perPositionHeightEnabled = defined(perPositionHeightProperty) && (perPositionHeightProperty.isConstant ? perPositionHeightProperty.getValue(Iso8601.MINIMUM_VALUE) : true); - return this._fillEnabled && !defined(polygon.height) && !defined(polygon.extrudedHeight) && isColorMaterial && + return this._fillEnabled && !defined(polygon.height) && !defined(polygon.extrudedHeight) &&// isColorMaterial && !perPositionHeightEnabled && GroundPrimitive.isSupported(this._scene); }; diff --git a/Source/DataSources/RectangleGeometryUpdater.js b/Source/DataSources/RectangleGeometryUpdater.js index 5ebe539f5df..a336bf4827c 100644 --- a/Source/DataSources/RectangleGeometryUpdater.js +++ b/Source/DataSources/RectangleGeometryUpdater.js @@ -167,9 +167,9 @@ define([ }; RectangleGeometryUpdater.prototype._isOnTerrain = function(entity, rectangle) { - var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; + //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; - return this._fillEnabled && !defined(rectangle.height) && !defined(rectangle.extrudedHeight) && isColorMaterial && GroundPrimitive.isSupported(this._scene); + return this._fillEnabled && !defined(rectangle.height) && !defined(rectangle.extrudedHeight) && GroundPrimitive.isSupported(this._scene); // && isColorMaterial; }; RectangleGeometryUpdater.prototype._isDynamic = function(entity, rectangle) { diff --git a/Source/DataSources/StaticGroundGeometryColorBatch.js b/Source/DataSources/StaticGroundGeometryColorBatch.js index 60bd9e63714..f172a81456e 100644 --- a/Source/DataSources/StaticGroundGeometryColorBatch.js +++ b/Source/DataSources/StaticGroundGeometryColorBatch.js @@ -1,353 +1,358 @@ define([ - '../Core/AssociativeArray', - '../Core/Color', - '../Core/defined', - '../Core/DistanceDisplayCondition', - '../Core/DistanceDisplayConditionGeometryInstanceAttribute', - '../Core/ShowGeometryInstanceAttribute', - '../Scene/GroundPrimitive', - './BoundingSphereState', - './Property' - ], function( - AssociativeArray, - Color, - defined, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, - ShowGeometryInstanceAttribute, - GroundPrimitive, - BoundingSphereState, - Property) { - 'use strict'; - - var colorScratch = new Color(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); - - function Batch(primitives, classificationType, color, key) { - this.primitives = primitives; - this.classificationType = classificationType; - this.color = color; - this.key = key; - this.createPrimitive = false; - this.waitingOnCreate = false; - this.primitive = undefined; - this.oldPrimitive = undefined; - this.geometry = new AssociativeArray(); - this.updaters = new AssociativeArray(); - this.updatersWithAttributes = new AssociativeArray(); - this.attributes = new AssociativeArray(); - this.subscriptions = new AssociativeArray(); - this.showsUpdated = new AssociativeArray(); - this.itemsToRemove = []; - this.isDirty = false; - } - - Batch.prototype.add = function(updater, instance) { - var id = updater.id; - this.createPrimitive = true; - this.geometry.set(id, instance); - this.updaters.set(id, updater); - if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { - this.updatersWithAttributes.set(id, updater); - } else { - var that = this; - this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { - if (propertyName === 'isShowing') { - that.showsUpdated.set(updater.id, updater); - } - })); - } - }; - - Batch.prototype.remove = function(updater) { - var id = updater.id; - this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; - if (this.updaters.remove(id)) { - this.updatersWithAttributes.remove(id); - var unsubscribe = this.subscriptions.get(id); - if (defined(unsubscribe)) { - unsubscribe(); - this.subscriptions.remove(id); + '../Core/AssociativeArray', + '../Core/Color', + '../Core/defined', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/GroundPrimitive', + './BoundingSphereState', + './Property' +], function( + AssociativeArray, + Color, + defined, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GroundPrimitive, + BoundingSphereState, + Property) { +'use strict'; + +var colorScratch = new Color(); +var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + +function Batch(primitives, appearanceType, classificationType, color, key) { + this.appearanceType = appearanceType; + this.primitives = primitives; + this.classificationType = classificationType; + this.color = color; + this.key = key; + this.createPrimitive = false; + this.waitingOnCreate = false; + this.primitive = undefined; + this.oldPrimitive = undefined; + this.geometry = new AssociativeArray(); + this.updaters = new AssociativeArray(); + this.updatersWithAttributes = new AssociativeArray(); + this.attributes = new AssociativeArray(); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); + this.itemsToRemove = []; + this.isDirty = false; +} + +Batch.prototype.add = function(updater, instance) { + var id = updater.id; + this.createPrimitive = true; + this.geometry.set(id, instance); + this.updaters.set(id, updater); + if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { + this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(updater.id, updater); } + })); + } +}; + +Batch.prototype.remove = function(updater) { + var id = updater.id; + this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); } - }; - - var scratchArray = new Array(4); - - Batch.prototype.update = function(time) { - var isUpdated = true; - var removedCount = 0; - var primitive = this.primitive; - var primitives = this.primitives; - var attributes; - var i; - - if (this.createPrimitive) { - var geometries = this.geometry.values; - var geometriesLength = geometries.length; - if (geometriesLength > 0) { - if (defined(primitive)) { - if (!defined(this.oldPrimitive)) { - this.oldPrimitive = primitive; - } else { - primitives.remove(primitive); - } - } - - for (i = 0; i < geometriesLength; i++) { - var geometryItem = geometries[i]; - var originalAttributes = geometryItem.attributes; - attributes = this.attributes.get(geometryItem.id.id); - - if (defined(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; - } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; - } - } - } - - primitive = new GroundPrimitive({ - asynchronous : true, - geometryInstances : geometries, - classificationType : this.classificationType - }); - primitives.add(primitive); - isUpdated = false; - } else { - if (defined(primitive)) { + } +}; + +var scratchArray = new Array(4); + +Batch.prototype.update = function(time) { + var isUpdated = true; + var removedCount = 0; + var primitive = this.primitive; + var primitives = this.primitives; + var attributes; + var i; + + if (this.createPrimitive) { + var geometries = this.geometry.values; + var geometriesLength = geometries.length; + if (geometriesLength > 0) { + if (defined(primitive)) { + if (!defined(this.oldPrimitive)) { + this.oldPrimitive = primitive; + } else { primitives.remove(primitive); - primitive = undefined; - } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; } } - this.attributes.removeAll(); - this.primitive = primitive; - this.createPrimitive = false; - this.waitingOnCreate = true; - } else if (defined(primitive) && primitive.ready) { - if (defined(this.oldPrimitive)) { - primitives.remove(this.oldPrimitive); - this.oldPrimitive = undefined; - } - var updatersWithAttributes = this.updatersWithAttributes.values; - var length = updatersWithAttributes.length; - var waitingOnCreate = this.waitingOnCreate; - for (i = 0; i < length; i++) { - var updater = updatersWithAttributes[i]; - var instance = this.geometry.get(updater.id); - - attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } + for (i = 0; i < geometriesLength; i++) { + var geometryItem = geometries[i]; + var originalAttributes = geometryItem.attributes; + attributes = this.attributes.get(geometryItem.id.id); - if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { - var colorProperty = updater.fillMaterialProperty.color; - colorProperty.getValue(time, colorScratch); - - if (!Color.equals(attributes._lastColor, colorScratch)) { - attributes._lastColor = Color.clone(colorScratch, attributes._lastColor); - var color = this.color; - var newColor = colorScratch.toBytes(scratchArray); - if (color[0] !== newColor[0] || color[1] !== newColor[1] || - color[2] !== newColor[2] || color[3] !== newColor[3]) { - this.itemsToRemove[removedCount++] = updater; - } + if (defined(attributes)) { + if (defined(originalAttributes.show)) { + originalAttributes.show.value = attributes.show; } - } - - var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); - } - - var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; - if (!Property.isConstant(distanceDisplayConditionProperty)) { - var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch); - if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { - attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); - attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + if (defined(originalAttributes.color)) { + originalAttributes.color.value = attributes.color; } } } - this.updateShows(primitive); - this.waitingOnCreate = false; - } else if (defined(primitive) && !primitive.ready) { + primitive = new GroundPrimitive({ + asynchronous : true, + geometryInstances : geometries, + classificationType : this.classificationType, + appearance : new this.appearanceType({ + flat : true + }) + }); + primitives.add(primitive); isUpdated = false; + } else { + if (defined(primitive)) { + primitives.remove(primitive); + primitive = undefined; + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } + } + + this.attributes.removeAll(); + this.primitive = primitive; + this.createPrimitive = false; + this.waitingOnCreate = true; + } else if (defined(primitive) && primitive.ready) { + if (defined(this.oldPrimitive)) { + primitives.remove(this.oldPrimitive); + this.oldPrimitive = undefined; } - this.itemsToRemove.length = removedCount; - return isUpdated; - }; - - Batch.prototype.updateShows = function(primitive) { - var showsUpdated = this.showsUpdated.values; - var length = showsUpdated.length; - for (var i = 0; i < length; i++) { - var updater = showsUpdated[i]; + var updatersWithAttributes = this.updatersWithAttributes.values; + var length = updatersWithAttributes.length; + var waitingOnCreate = this.waitingOnCreate; + for (i = 0; i < length; i++) { + var updater = updatersWithAttributes[i]; var instance = this.geometry.get(updater.id); - var attributes = this.attributes.get(instance.id.id); + attributes = this.attributes.get(instance.id.id); if (!defined(attributes)) { attributes = primitive.getGeometryInstanceAttributes(instance.id); this.attributes.set(instance.id.id, attributes); } - var show = updater.entity.isShowing; + if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { + var colorProperty = updater.fillMaterialProperty.color; + colorProperty.getValue(time, colorScratch); + + if (!Color.equals(attributes._lastColor, colorScratch)) { + attributes._lastColor = Color.clone(colorScratch, attributes._lastColor); + var color = this.color; + var newColor = colorScratch.toBytes(scratchArray); + if (color[0] !== newColor[0] || color[1] !== newColor[1] || + color[2] !== newColor[2] || color[3] !== newColor[3]) { + this.itemsToRemove[removedCount++] = updater; + } + } + } + + var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); var currentShow = attributes.show[0] === 1; if (show !== currentShow) { attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } - } - this.showsUpdated.removeAll(); - }; - Batch.prototype.contains = function(updater) { - return this.updaters.contains(updater.id); - }; + var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; + if (!Property.isConstant(distanceDisplayConditionProperty)) { + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch); + if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { + attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); + attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + } + } + } - Batch.prototype.getBoundingSphere = function(updater, result) { - var primitive = this.primitive; - if (!primitive.ready) { - return BoundingSphereState.PENDING; + this.updateShows(primitive); + this.waitingOnCreate = false; + } else if (defined(primitive) && !primitive.ready) { + isUpdated = false; + } + this.itemsToRemove.length = removedCount; + return isUpdated; +}; + +Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var instance = this.geometry.get(updater.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); } - var bs = primitive.getBoundingSphere(updater.entity); - if (!defined(bs)) { - return BoundingSphereState.FAILED; + var show = updater.entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } + } + this.showsUpdated.removeAll(); +}; - bs.clone(result); - return BoundingSphereState.DONE; - }; +Batch.prototype.contains = function(updater) { + return this.updaters.contains(updater.id); +}; - Batch.prototype.removeAllPrimitives = function() { - var primitives = this.primitives; +Batch.prototype.getBoundingSphere = function(updater, result) { + var primitive = this.primitive; + if (!primitive.ready) { + return BoundingSphereState.PENDING; + } - var primitive = this.primitive; - if (defined(primitive)) { - primitives.remove(primitive); - this.primitive = undefined; - this.geometry.removeAll(); - this.updaters.removeAll(); - } + var bs = primitive.getBoundingSphere(updater.entity); + if (!defined(bs)) { + return BoundingSphereState.FAILED; + } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; - } - }; - - /** - * @private - */ - function StaticGroundGeometryColorBatch(primitives, classificationType) { - this._batches = new AssociativeArray(); - this._primitives = primitives; - this._classificationType = classificationType; + bs.clone(result); + return BoundingSphereState.DONE; +}; + +Batch.prototype.removeAllPrimitives = function() { + var primitives = this.primitives; + + var primitive = this.primitive; + if (defined(primitive)) { + primitives.remove(primitive); + this.primitive = undefined; + this.geometry.removeAll(); + this.updaters.removeAll(); } - StaticGroundGeometryColorBatch.prototype.add = function(time, updater) { - var instance = updater.createFillGeometryInstance(time); - var batches = this._batches; - // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key - var batchKey = 1;//new Uint32Array(instance.attributes.color.value.buffer)[0]; - var batch; - if (batches.contains(batchKey)) { - batch = batches.get(batchKey); - } else { - batch = new Batch(this._primitives, this._classificationType, instance.attributes.color.value, batchKey); - batches.set(batchKey, batch); - } - batch.add(updater, instance); - return batch; - }; - - StaticGroundGeometryColorBatch.prototype.remove = function(updater) { - var batchesArray = this._batches.values; - var count = batchesArray.length; - for (var i = 0; i < count; ++i) { - if (batchesArray[i].remove(updater)) { - return; - } - } - }; - - StaticGroundGeometryColorBatch.prototype.update = function(time) { - var i; - var updater; - - //Perform initial update - var isUpdated = true; - var batches = this._batches; - var batchesArray = batches.values; - var batchCount = batchesArray.length; - for (i = 0; i < batchCount; ++i) { - isUpdated = batchesArray[i].update(time) && isUpdated; + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } +}; + +/** + * @private + */ +function StaticGroundGeometryColorBatch(primitives, appearanceType, classificationType) { + this._batches = new AssociativeArray(); + this._primitives = primitives; + this._classificationType = classificationType; + this._appearanceType = appearanceType; +} + +StaticGroundGeometryColorBatch.prototype.add = function(time, updater) { + var instance = updater.createFillGeometryInstance(time); + var batches = this._batches; + // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key + var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0]; + var batch; + if (batches.contains(batchKey)) { + batch = batches.get(batchKey); + } else { + batch = new Batch(this._primitives, this._appearanceType, this._classificationType, instance.attributes.color.value, batchKey); + batches.set(batchKey, batch); + } + batch.add(updater, instance); + return batch; +}; + +StaticGroundGeometryColorBatch.prototype.remove = function(updater) { + var batchesArray = this._batches.values; + var count = batchesArray.length; + for (var i = 0; i < count; ++i) { + if (batchesArray[i].remove(updater)) { + return; } + } +}; + +StaticGroundGeometryColorBatch.prototype.update = function(time) { + var i; + var updater; + + //Perform initial update + var isUpdated = true; + var batches = this._batches; + var batchesArray = batches.values; + var batchCount = batchesArray.length; + for (i = 0; i < batchCount; ++i) { + isUpdated = batchesArray[i].update(time) && isUpdated; + } - //If any items swapped between batches we need to move them - for (i = 0; i < batchCount; ++i) { - var oldBatch = batchesArray[i]; - var itemsToRemove = oldBatch.itemsToRemove; - var itemsToMoveLength = itemsToRemove.length; - for (var j = 0; j < itemsToMoveLength; j++) { - updater = itemsToRemove[j]; - oldBatch.remove(updater); - var newBatch = this.add(time, updater); - oldBatch.isDirty = true; - newBatch.isDirty = true; - } + //If any items swapped between batches we need to move them + for (i = 0; i < batchCount; ++i) { + var oldBatch = batchesArray[i]; + var itemsToRemove = oldBatch.itemsToRemove; + var itemsToMoveLength = itemsToRemove.length; + for (var j = 0; j < itemsToMoveLength; j++) { + updater = itemsToRemove[j]; + oldBatch.remove(updater); + var newBatch = this.add(time, updater); + oldBatch.isDirty = true; + newBatch.isDirty = true; } + } - //If we moved anything around, we need to re-build the primitive and remove empty batches - var batchesArrayCopy = batchesArray.slice(); - var batchesCopyCount = batchesArrayCopy.length; - for (i = 0; i < batchesCopyCount; ++i) { - var batch = batchesArrayCopy[i]; - if (batch.isDirty) { - isUpdated = batchesArrayCopy[i].update(time) && isUpdated; - batch.isDirty = false; - } - if (batch.geometry.length === 0) { - batches.remove(batch.key); - } + //If we moved anything around, we need to re-build the primitive and remove empty batches + var batchesArrayCopy = batchesArray.slice(); + var batchesCopyCount = batchesArrayCopy.length; + for (i = 0; i < batchesCopyCount; ++i) { + var batch = batchesArrayCopy[i]; + if (batch.isDirty) { + isUpdated = batchesArrayCopy[i].update(time) && isUpdated; + batch.isDirty = false; + } + if (batch.geometry.length === 0) { + batches.remove(batch.key); } + } - return isUpdated; - }; + return isUpdated; +}; - StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(updater, result) { - var batchesArray = this._batches.values; - var batchCount = batchesArray.length; - for (var i = 0; i < batchCount; ++i) { - var batch = batchesArray[i]; - if (batch.contains(updater)) { - return batch.getBoundingSphere(updater, result); - } +StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(updater, result) { + var batchesArray = this._batches.values; + var batchCount = batchesArray.length; + for (var i = 0; i < batchCount; ++i) { + var batch = batchesArray[i]; + if (batch.contains(updater)) { + return batch.getBoundingSphere(updater, result); } + } - return BoundingSphereState.FAILED; - }; + return BoundingSphereState.FAILED; +}; - StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { - var batchesArray = this._batches.values; - var batchCount = batchesArray.length; - for (var i = 0; i < batchCount; ++i) { - batchesArray[i].removeAllPrimitives(); - } - }; +StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { + var batchesArray = this._batches.values; + var batchCount = batchesArray.length; + for (var i = 0; i < batchCount; ++i) { + batchesArray[i].removeAllPrimitives(); + } +}; - return StaticGroundGeometryColorBatch; +return StaticGroundGeometryColorBatch; }); diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js new file mode 100644 index 00000000000..85c2c61b2ca --- /dev/null +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -0,0 +1,386 @@ +define([ + '../Core/AssociativeArray', + '../Core/Color', + '../Core/ColorGeometryInstanceAttribute', + '../Core/defined', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/GroundPrimitive', + './BoundingSphereState', + './ColorMaterialProperty', + './MaterialProperty', + './Property' + ], function( + AssociativeArray, + Color, + ColorGeometryInstanceAttribute, + defined, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GroundPrimitive, + BoundingSphereState, + ColorMaterialProperty, + MaterialProperty, + Property) { + 'use strict'; + + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + + // Encapsulates a Primitive and all the entities that it represents. + function Batch(primitives, appearanceType, materialProperty, shadows) { + this.primitives = primitives; // scene level primitive collection + this.appearanceType = appearanceType; + this.materialProperty = materialProperty; + this.updaters = new AssociativeArray(); + this.createPrimitive = true; + this.primitive = undefined; + this.oldPrimitive = undefined; + this.geometry = new AssociativeArray(); + this.material = undefined; + this.updatersWithAttributes = new AssociativeArray(); + this.attributes = new AssociativeArray(); + this.invalidated = false; + this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); + this.shadows = shadows; + } + + Batch.prototype.onMaterialChanged = function() { + this.invalidated = true; + }; + + Batch.prototype.nonOverlapping = function(updater) { + return false; + }; + + Batch.prototype.isMaterial = function(updater) { + var material = this.materialProperty; + var updaterMaterial = updater.fillMaterialProperty; + + if (updaterMaterial === material) { + return true; + } + return defined(material) && material.equals(updaterMaterial); + }; + + Batch.prototype.add = function(time, updater) { + var id = updater.id; + this.updaters.set(id, updater); + this.geometry.set(id, updater.createFillGeometryInstance(time)); + if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { + this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(updater.id, updater); + } + })); + } + this.createPrimitive = true; + }; + + Batch.prototype.remove = function(updater) { + var id = updater.id; + this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + } + return this.createPrimitive; + }; + + var colorScratch = new Color(); + + Batch.prototype.update = function(time) { + var isUpdated = true; + var primitive = this.primitive; + var primitives = this.primitives; + var geometries = this.geometry.values; + var attributes; + var i; + + if (this.createPrimitive) { + var geometriesLength = geometries.length; + if (geometriesLength > 0) { + if (defined(primitive)) { + if (!defined(this.oldPrimitive)) { + this.oldPrimitive = primitive; + } else { + primitives.remove(primitive); + } + } + + for (i = 0; i < geometriesLength; i++) { + var geometry = geometries[i]; + var originalAttributes = geometry.attributes; + attributes = this.attributes.get(geometry.id.id); + + if (defined(attributes)) { + if (defined(originalAttributes.show)) { + originalAttributes.show.value = attributes.show; + } + if (defined(originalAttributes.color)) { + originalAttributes.color.value = attributes.color; + } + if (defined(originalAttributes.depthFailColor)) { + originalAttributes.depthFailColor.value = attributes.depthFailColor; + } + } + } + + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + + var depthFailAppearance; + if (defined(this.depthFailMaterialProperty)) { + var translucent; + if (this.depthFailMaterialProperty instanceof MaterialProperty) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + translucent = this.depthFailMaterial.isTranslucent(); + } else { + translucent = this.material.isTranslucent(); + } + depthFailAppearance = new this.depthFailAppearanceType({ + material : this.depthFailMaterial, + translucent : translucent, + closed : this.closed + }); + } + + primitive = new GroundPrimitive({ + asynchronous : true, + geometryInstances : geometries, + appearance : new this.appearanceType({ + material : this.material, + translucent : this.material.isTranslucent(), + closed : this.closed + }), + depthFailAppearance : depthFailAppearance, + shadows : this.shadows + }); + + primitives.add(primitive); + isUpdated = false; + } else { + if (defined(primitive)) { + primitives.remove(primitive); + primitive = undefined; + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } + } + + this.attributes.removeAll(); + this.primitive = primitive; + this.createPrimitive = false; + } else if (defined(primitive) && primitive.ready) { + if (defined(this.oldPrimitive)) { + primitives.remove(this.oldPrimitive); + this.oldPrimitive = undefined; + } + + this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); + this.primitive.appearance.material = this.material; + + if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { + this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); + this.primitive.depthFailAppearance.material = this.depthFailMaterial; + } + + var updatersWithAttributes = this.updatersWithAttributes.values; + var length = updatersWithAttributes.length; + for (i = 0; i < length; i++) { + var updater = updatersWithAttributes[i]; + var entity = updater.entity; + var instance = this.geometry.get(updater.id); + + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && !updater.depthFailMaterialProperty.isConstant) { + var depthFailColorProperty = updater.depthFailMaterialProperty.color; + depthFailColorProperty.getValue(time, colorScratch); + if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { + attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); + attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); + } + } + + var show = entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + + var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; + if (!Property.isConstant(distanceDisplayConditionProperty)) { + var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch); + if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { + attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); + attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + } + } + } + + this.updateShows(primitive); + } else if (defined(primitive) && !primitive.ready) { + isUpdated = false; + } + return isUpdated; + }; + + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var entity = updater.entity; + var instance = this.geometry.get(updater.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + + Batch.prototype.contains = function(updater) { + return this.updaters.contains(updater.id); + }; + + Batch.prototype.getBoundingSphere = function(updater, result) { + var primitive = this.primitive; + if (!primitive.ready) { + return BoundingSphereState.PENDING; + } + var attributes = primitive.getGeometryInstanceAttributes(updater.entity); + if (!defined(attributes) || !defined(attributes.boundingSphere) || + (defined(attributes.show) && attributes.show[0] === 0)) { + return BoundingSphereState.FAILED; + } + attributes.boundingSphere.clone(result); + return BoundingSphereState.DONE; + }; + + Batch.prototype.destroy = function() { + var primitive = this.primitive; + var primitives = this.primitives; + if (defined(primitive)) { + primitives.remove(primitive); + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + } + this.removeMaterialSubscription(); + }; + + /** + * @private + */ + function StaticGroundGeometryPerMaterialBatch(primitives, appearanceType, shadows) { + this._items = []; + this._primitives = primitives; + this._appearanceType = appearanceType; + this._shadows = shadows; + } + + StaticGroundGeometryPerMaterialBatch.prototype.add = function(time, updater) { + var items = this._items; + var length = items.length; + for (var i = 0; i < length; i++) { + var item = items[i]; + if (item.isMaterial(updater) && item.nonOverlapping(updater)) { + item.add(time, updater); + return; + } + } + var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._shadows); + batch.add(time, updater); + items.push(batch); + }; + + StaticGroundGeometryPerMaterialBatch.prototype.remove = function(updater) { + var items = this._items; + var length = items.length; + for (var i = length - 1; i >= 0; i--) { + var item = items[i]; + if (item.remove(updater)) { + if (item.updaters.length === 0) { + items.splice(i, 1); + item.destroy(); + } + break; + } + } + }; + + StaticGroundGeometryPerMaterialBatch.prototype.update = function(time) { + var i; + var items = this._items; + var length = items.length; + + for (i = length - 1; i >= 0; i--) { + var item = items[i]; + if (item.invalidated) { + items.splice(i, 1); + var updaters = item.updaters.values; + var updatersLength = updaters.length; + for (var h = 0; h < updatersLength; h++) { + this.add(time, updaters[h]); + } + item.destroy(); + } + } + + var isUpdated = true; + for (i = 0; i < length; i++) { + isUpdated = items[i].update(time) && isUpdated; + } + return isUpdated; + }; + + StaticGroundGeometryPerMaterialBatch.prototype.getBoundingSphere = function(updater, result) { + var items = this._items; + var length = items.length; + for (var i = 0; i < length; i++) { + var item = items[i]; + if(item.contains(updater)){ + return item.getBoundingSphere(updater, result); + } + } + return BoundingSphereState.FAILED; + }; + + StaticGroundGeometryPerMaterialBatch.prototype.removeAllPrimitives = function() { + var items = this._items; + var length = items.length; + for (var i = 0; i < length; i++) { + items[i].destroy(); + } + this._items.length = 0; + }; + + return StaticGroundGeometryPerMaterialBatch; +}); diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index f9566a88168..8c6b002e2e1 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -1,5 +1,6 @@ define([ '../Core/ColorGeometryInstanceAttribute', + '../Core/combine', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -20,6 +21,8 @@ define([ './BlendingState', './ClassificationType', './DepthFunction', + './Material', + './MaterialAppearance', './PerInstanceColorAppearance', './Primitive', './SceneMode', @@ -28,6 +31,7 @@ define([ '../Core/WebGLConstants' ], function( ColorGeometryInstanceAttribute, + combine, defaultValue, defined, defineProperties, @@ -48,6 +52,8 @@ define([ BlendingState, ClassificationType, DepthFunction, + Material, + MaterialAppearance, PerInstanceColorAppearance, Primitive, SceneMode, @@ -65,8 +71,7 @@ define([ * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix - * and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance} - * is supported at this time. + * and match most of them and add a new geometry or appearance independently of each other. *

*

* For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there @@ -85,6 +90,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. This can either be a single instance or an array of length one. + * @param {Appearance} [options.appearance] The appearance used to render the primitive. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. @@ -104,6 +110,7 @@ define([ */ function ClassificationPrimitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var geometryInstances = options.geometryInstances; /** * The geometry instance rendered with this primitive. This may @@ -123,7 +130,7 @@ define([ * * @default undefined */ - this.geometryInstances = options.geometryInstances; + this.geometryInstances = geometryInstances; /** * Determines if the primitive will be shown. This affects all geometry * instances in the primitive. @@ -187,12 +194,38 @@ define([ this._primitive = undefined; this._pickPrimitive = options._pickPrimitive; - var appearance = new PerInstanceColorAppearance({ - flat : true - }); + var appearance = options.appearance; + + // Require SphericalExtents attribute on all geometries if material isn't PerInstanceColor + if (defined(appearance) && defined(appearance.material) && defined(geometryInstances)) { + var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; + var geometryInstanceCount = geometryInstances.length; + for (var i = 0; i < geometryInstanceCount; i++) { + var attributes = geometryInstances[i].attributes; + if (!defined(attributes) || !defined(attributes.sphericalExtents)) { + throw new DeveloperError('Materials on ClassificationPrimitives require sphericalExtents attribute'); + } + } + } + + // If attributes include color and appearance is undefined, then default to a color appearance + if (!defined(appearance)) { + var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; + var geometryInstanceCount = geometryInstances.length; + for (var i = 0; i < geometryInstanceCount; i++) { + var attributes = geometryInstances[i].attributes; + if (defined(attributes) && defined(attributes.color)) { + appearance = new PerInstanceColorAppearance({ + flat : true + }); + break; + } + } + } + this.appearance = appearance; var readOnlyAttributes; - if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { + if (defined(geometryInstances) && isArray(geometryInstances) && geometryInstances.length > 1) { readOnlyAttributes = ClassificationPrimitiveReadOnlyInstanceAttributes; } @@ -517,10 +550,6 @@ define([ } function createShaderProgram(classificationPrimitive, frameState, appearance) { - if (defined(classificationPrimitive._sp)) { - return; - } - var context = frameState.context; var primitive = classificationPrimitive._primitive; var vs = ShadowVolumeVS; @@ -597,14 +626,33 @@ define([ attributeLocations : attributeLocations }); + var appearance = classificationPrimitive.appearance; + var isPerInstanceColor = appearance instanceof PerInstanceColorAppearance; + + var vsColorSource = new ShaderSource({ + defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : [], + sources : [vs] + }); + + var parts; + if (isPerInstanceColor) { + parts = [ShadowVolumeColorFS]; + } else { + // Modify fsColorSource for material (yay!) + // Only have to modify the FS b/c all material hookups happen in there b/c lol VS + // TODO: scan shaderSource to determine what material inputs are needed for ShadowVolumeColorFS? + parts = [appearance.material.shaderSource, ShadowVolumeColorFS]; + } + var fsColorSource = new ShaderSource({ - sources : [ShadowVolumeColorFS] + defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : [], + sources : [parts.join('\n')] }); classificationPrimitive._spColor = ShaderProgram.replaceCache({ context : context, shaderProgram : classificationPrimitive._spColor, - vertexShaderSource : vsSource, + vertexShaderSource : vsColorSource, fragmentShaderSource : fsColorSource, attributeLocations : attributeLocations }); @@ -663,6 +711,13 @@ define([ command.vertexArray = vertexArray; command.renderState = classificationPrimitive._rsColorPass; command.shaderProgram = classificationPrimitive._spColor; + + var appearance = classificationPrimitive.appearance; + var material = appearance.material; + if (defined(material)) { + uniformMap = combine(uniformMap, material._uniforms) + } + command.uniformMap = uniformMap; } @@ -857,6 +912,11 @@ define([ return; } + var appearance = this.appearance; + if (defined(appearance) && defined(appearance.material)) { + appearance.material.update(frameState.context); + } + var that = this; var primitiveOptions = this._primitiveOptions; @@ -949,6 +1009,8 @@ define([ this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); } + // Update primitive appearance + this._primitive.appearance = appearance; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 22db4e87f1c..821169e3dfd 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -57,8 +57,7 @@ define([ * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix - * and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance} - * is supported at this time. + * and match most of them and add a new geometry or appearance independently of each other. *

*

* For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there @@ -73,6 +72,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. + * @param {Appearance} [options.appearance] The appearance used to render the primitive. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. @@ -134,6 +134,7 @@ define([ function GroundPrimitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this.appearance = options.appearance; /** * The geometry instance rendered with this primitive. This may * be undefined if options.releaseGeometryInstances @@ -200,7 +201,7 @@ define([ this._ready = false; this._readyPromise = when.defer(); - this._primitive = undefined; + this._classificationPrimitive = undefined; this._maxHeight = undefined; this._minHeight = undefined; @@ -219,8 +220,9 @@ define([ this._uniformMap = uniformMap; var that = this; - this._primitiveOptions = { + this._classificationPrimitiveOptions = { geometryInstances : undefined, + appearance : undefined, vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), interleave : defaultValue(options.interleave, false), releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), @@ -248,7 +250,7 @@ define([ */ vertexCacheOptimize : { get : function() { - return this._primitiveOptions.vertexCacheOptimize; + return this._classificationPrimitiveOptions.vertexCacheOptimize; } }, @@ -264,7 +266,7 @@ define([ */ interleave : { get : function() { - return this._primitiveOptions.interleave; + return this._classificationPrimitiveOptions.interleave; } }, @@ -280,7 +282,7 @@ define([ */ releaseGeometryInstances : { get : function() { - return this._primitiveOptions.releaseGeometryInstances; + return this._classificationPrimitiveOptions.releaseGeometryInstances; } }, @@ -296,7 +298,7 @@ define([ */ allowPicking : { get : function() { - return this._primitiveOptions.allowPicking; + return this._classificationPrimitiveOptions.allowPicking; } }, @@ -312,7 +314,7 @@ define([ */ asynchronous : { get : function() { - return this._primitiveOptions.asynchronous; + return this._classificationPrimitiveOptions.asynchronous; } }, @@ -328,7 +330,7 @@ define([ */ compressVertices : { get : function() { - return this._primitiveOptions.compressVertices; + return this._classificationPrimitiveOptions.compressVertices; } }, @@ -616,7 +618,7 @@ define([ } if (frameState.invertClassification) { - var ignoreShowCommands = groundPrimitive._primitive._commandsIgnoreShow; + var ignoreShowCommands = groundPrimitive._classificationPrimitive._commandsIgnoreShow; var ignoreShowCommandsLength = ignoreShowCommands.length; for (i = 0; i < ignoreShowCommandsLength; ++i) { @@ -634,7 +636,7 @@ define([ if (passes.pick) { var pickLength = pickCommands.length; - var primitive = groundPrimitive._primitive._primitive; + var primitive = groundPrimitive._classificationPrimitive._primitive; var pickOffsets = primitive._pickOffsets; for (var j = 0; j < pickLength; ++j) { var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)]; @@ -689,7 +691,7 @@ define([ * @exception {DeveloperError} Not all of the geometry instances have the same color attribute. */ GroundPrimitive.prototype.update = function(frameState) { - if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { + if (!this.show || (!defined(this._classificationPrimitive) && !defined(this.geometryInstances))) { return; } @@ -705,9 +707,9 @@ define([ } var that = this; - var primitiveOptions = this._primitiveOptions; + var primitiveOptions = this._classificationPrimitiveOptions; - if (!defined(this._primitive)) { + if (!defined(this._classificationPrimitive)) { var ellipsoid = frameState.mapProjection.ellipsoid; var instance; @@ -756,9 +758,8 @@ define([ geometry = instance.geometry; instanceType = geometry.constructor; - // TODO: what to do if not all the geometryInstances are polygonHierarchies? TODO: does this even happen here? var attributes = { - sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(geometry._rectangle) + sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(getRectangle(frameState, geometry)) }; var instanceAttributes = instance.attributes; for (var attributeKey in instanceAttributes) { @@ -776,6 +777,7 @@ define([ } primitiveOptions.geometryInstances = groundInstances; + primitiveOptions.appearance = this.appearance; primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { createBoundingVolume(that, frameState, geometry); @@ -784,8 +786,8 @@ define([ updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); }; - this._primitive = new ClassificationPrimitive(primitiveOptions); - this._primitive.readyPromise.then(function(primitive) { + this._classificationPrimitive = new ClassificationPrimitive(primitiveOptions); + this._classificationPrimitive.readyPromise.then(function(primitive) { that._ready = true; if (that.releaseGeometryInstances) { @@ -801,9 +803,10 @@ define([ }); } - this._primitive.debugShowShadowVolume = this.debugShowShadowVolume; - this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; - this._primitive.update(frameState); + this._classificationPrimitive.appearance = this.appearance; + this._classificationPrimitive.debugShowShadowVolume = this.debugShowShadowVolume; + this._classificationPrimitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._classificationPrimitive.update(frameState); }; /** @@ -833,11 +836,11 @@ define([ */ GroundPrimitive.prototype.getGeometryInstanceAttributes = function(id) { //>>includeStart('debug', pragmas.debug); - if (!defined(this._primitive)) { + if (!defined(this._classificationPrimitive)) { throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); } //>>includeEnd('debug'); - return this._primitive.getGeometryInstanceAttributes(id); + return this._classificationPrimitive.getGeometryInstanceAttributes(id); }; /** @@ -874,7 +877,7 @@ define([ * @see GroundPrimitive#isDestroyed */ GroundPrimitive.prototype.destroy = function() { - this._primitive = this._primitive && this._primitive.destroy(); + this._classificationPrimitive = this._classificationPrimitive && this._classificationPrimitive.destroy(); return destroyObject(this); }; diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl index 7b1054b4acc..647a5120908 100644 --- a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl @@ -5,29 +5,30 @@ varying vec2 v_st; void main() { czm_materialInput materialInput; - + vec3 normalEC = normalize(czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0))); #ifdef FACE_FORWARD normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif - + materialInput.s = v_st.s; materialInput.st = v_st; materialInput.str = vec3(v_st, 0.0); - + // Convert tangent space material normal to eye space materialInput.normalEC = normalEC; materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC); - + // Convert view vector to world space - vec3 positionToEyeEC = -v_positionEC; + vec3 positionToEyeEC = -v_positionEC; materialInput.positionToEyeEC = positionToEyeEC; czm_material material = czm_getMaterial(materialInput); - -#ifdef FLAT + +#ifdef FLAT gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #else gl_FragColor = czm_phong(normalize(positionToEyeEC), material); #endif + //gl_FragColor = vec4(positionToEyeEC, 1.0); } diff --git a/Source/Shaders/Materials/CheckerboardMaterial.glsl b/Source/Shaders/Materials/CheckerboardMaterial.glsl index 56d1fdb230a..5c7a566c313 100644 --- a/Source/Shaders/Materials/CheckerboardMaterial.glsl +++ b/Source/Shaders/Materials/CheckerboardMaterial.glsl @@ -7,22 +7,22 @@ czm_material czm_getMaterial(czm_materialInput materialInput) czm_material material = czm_getDefaultMaterial(materialInput); vec2 st = materialInput.st; - + // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights float b = mod(floor(repeat.s * st.s) + floor(repeat.t * st.t), 2.0); // 0.0 or 1.0 - + // Find the distance from the closest separator (region between two colors) float scaledWidth = fract(repeat.s * st.s); scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5)); float scaledHeight = fract(repeat.t * st.t); scaledHeight = abs(scaledHeight - floor(scaledHeight + 0.5)); float value = min(scaledWidth, scaledHeight); - + vec4 currentColor = mix(lightColor, darkColor, b); vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03); - + material.diffuse = color.rgb; material.alpha = color.a; - + return material; } diff --git a/Source/Shaders/ShadowVolumeColorFS.glsl b/Source/Shaders/ShadowVolumeColorFS.glsl index 344aad27c39..682f3a3699e 100644 --- a/Source/Shaders/ShadowVolumeColorFS.glsl +++ b/Source/Shaders/ShadowVolumeColorFS.glsl @@ -5,10 +5,13 @@ #ifdef VECTOR_TILE uniform vec4 u_highlightColor; #else -varying vec4 v_color; varying vec4 v_sphericalExtents; #endif +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#endif + float rez = 0.1; // http://developer.download.nvidia.com/cg/atan2.html @@ -47,19 +50,25 @@ float completelyFakeAsin(float x) return (x * x * x + x) * 0.78539816339; } -void main(void) -{ -#ifdef VECTOR_TILE - gl_FragColor = u_highlightColor; -#else - vec2 coords = gl_FragCoord.xy / czm_viewport.zw; +vec3 getWorldPos(vec2 fragCoord) { + vec2 coords = fragCoord / czm_viewport.zw; float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - - vec4 windowCoord = vec4(gl_FragCoord.xy, depth, 1.0); + vec4 windowCoord = vec4(fragCoord, depth, 1.0); vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); vec4 worldCoord4 = czm_inverseView * eyeCoord; vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; + return worldCoord; +} +void main(void) +{ +#ifdef VECTOR_TILE + gl_FragColor = u_highlightColor; +#else + #ifdef PER_INSTANCE_COLOR + gl_FragColor = v_color; + #else + vec3 worldCoord = getWorldPos(gl_FragCoord.xy); vec3 sphereNormal = normalize(worldCoord); float latitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella @@ -68,16 +77,42 @@ void main(void) float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w; float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z; + /* if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { discard; - } else { - // UV checkerboard - if (((mod(floor(u / rez), 2.0) == 1.0) && (mod(floor(v / rez), 2.0) == 0.0)) || ((mod(floor(u / rez), 2.0) == 0.0) && (mod(floor(v / rez), 2.0) == 1.0))) { - gl_FragColor = vec4(u, v, 0.0, 1.0); - } else { - gl_FragColor = v_color; - } - } + }*/ + + vec3 positionToEyeEC = -(czm_modelView * vec4(worldCoord, 1.0)).xyz; + + // compute normal + vec2 fragCoord = gl_FragCoord.xy; + float d = 1.0; + + // sample up, down, left, and right in screen space + vec3 downUp = getWorldPos(fragCoord + vec2(0.0, d)) - getWorldPos(fragCoord - vec2(0.0, d)); + vec3 leftRight = getWorldPos(fragCoord - vec2(d, 0.0)) - getWorldPos(fragCoord + vec2(d, 0.0)); + vec3 normal = normalize(cross(downUp, leftRight)); + + //vec3 normal = sphereNormal; // TODO: do better? + + // TODO: might need optional rotations down here... + vec3 normalEC = czm_normal * normal; + vec3 tangent = cross(vec3(0, 0, 1), normal); + vec3 tangentEC = czm_normal * tangent; + vec3 bitangentEC = czm_normal * cross(normal, tangent); + + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(normalEC, tangentEC, bitangentEC); + //materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC); + materialInput.positionToEyeEC = positionToEyeEC; + materialInput.st.x = v; + materialInput.st.y = u; + czm_material material = czm_getMaterial(materialInput); + + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); + //gl_FragColor = vec4(u, v, 0.0, 1.0); + #endif #endif czm_writeDepthClampedToFarPlane(); diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index 51e951b07b9..777150ea67e 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -4,8 +4,8 @@ #ifdef VECTOR_TILE uniform vec4 u_highlightColor; -#else -varying vec4 v_color; +//#else +//varying vec4 v_color; #endif void main(void) @@ -13,7 +13,7 @@ void main(void) #ifdef VECTOR_TILE gl_FragColor = u_highlightColor; #else - gl_FragColor = v_color; + gl_FragColor = vec4(1.0); #endif czm_writeDepthClampedToFarPlane(); } diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index bb7721936ed..7e89b63a114 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -6,7 +6,7 @@ uniform mat4 u_modifiedModelViewProjection; #else attribute vec3 position3DHigh; attribute vec3 position3DLow; -attribute vec4 color; +//attribute vec4 color; attribute float batchId; #endif @@ -17,16 +17,23 @@ uniform float u_globeMinimumAltitude; #endif #ifndef VECTOR_TILE -varying vec4 v_color; varying vec4 v_sphericalExtents; #endif +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#endif + void main() { #ifdef VECTOR_TILE gl_Position = czm_depthClampFarPlane(u_modifiedModelViewProjection * vec4(position, 1.0)); #else - v_color = color; + +#ifdef PER_INSTANCE_COLOR + v_color = czm_batchTable_color(batchId); +#endif + v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); vec4 position = czm_computePosition(); From 0433738200d7bedeacac749ad70c91307beee742 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 21 Mar 2018 10:45:14 -0400 Subject: [PATCH 06/40] change ground primitive normal computation to EC for less noise --- Source/Shaders/ShadowVolumeColorFS.glsl | 38 ++++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Source/Shaders/ShadowVolumeColorFS.glsl b/Source/Shaders/ShadowVolumeColorFS.glsl index 682f3a3699e..d6600295573 100644 --- a/Source/Shaders/ShadowVolumeColorFS.glsl +++ b/Source/Shaders/ShadowVolumeColorFS.glsl @@ -50,14 +50,17 @@ float completelyFakeAsin(float x) return (x * x * x + x) * 0.78539816339; } -vec3 getWorldPos(vec2 fragCoord) { +vec4 getEyeCoord(vec2 fragCoord) { vec2 coords = fragCoord / czm_viewport.zw; float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); vec4 windowCoord = vec4(fragCoord, depth, 1.0); vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); - vec4 worldCoord4 = czm_inverseView * eyeCoord; - vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; - return worldCoord; + return eyeCoord; +} + +vec3 getEyeCoord3(vec2 fragCoord) { + vec4 eyeCoord = getEyeCoord(fragCoord); + return eyeCoord.xyz / eyeCoord.w; } void main(void) @@ -68,7 +71,11 @@ void main(void) #ifdef PER_INSTANCE_COLOR gl_FragColor = v_color; #else - vec3 worldCoord = getWorldPos(gl_FragCoord.xy); + + vec4 eyeCoord = getEyeCoord(gl_FragCoord.xy); + vec4 worldCoord4 = czm_inverseView * eyeCoord; + vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; + vec3 sphereNormal = normalize(worldCoord); float latitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella @@ -88,30 +95,21 @@ void main(void) vec2 fragCoord = gl_FragCoord.xy; float d = 1.0; - // sample up, down, left, and right in screen space - vec3 downUp = getWorldPos(fragCoord + vec2(0.0, d)) - getWorldPos(fragCoord - vec2(0.0, d)); - vec3 leftRight = getWorldPos(fragCoord - vec2(d, 0.0)) - getWorldPos(fragCoord + vec2(d, 0.0)); - vec3 normal = normalize(cross(downUp, leftRight)); - - //vec3 normal = sphereNormal; // TODO: do better? - - // TODO: might need optional rotations down here... - vec3 normalEC = czm_normal * normal; - vec3 tangent = cross(vec3(0, 0, 1), normal); - vec3 tangentEC = czm_normal * tangent; - vec3 bitangentEC = czm_normal * cross(normal, tangent); + // sample adjacent pixels in 2x2 block in screen space + vec3 eyeCoord3 = eyeCoord.xyz / eyeCoord.w; + vec3 downUp = eyeCoord3 - getEyeCoord3(fragCoord - vec2(0.0, d)).xyz; + vec3 leftRight = getEyeCoord3(fragCoord - vec2(d, 0.0)).xyz - eyeCoord3; + vec3 normalEC = normalize(cross(downUp, leftRight)); czm_materialInput materialInput; materialInput.normalEC = normalEC; - materialInput.tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(normalEC, tangentEC, bitangentEC); - //materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC); + materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC); materialInput.positionToEyeEC = positionToEyeEC; materialInput.st.x = v; materialInput.st.y = u; czm_material material = czm_getMaterial(materialInput); gl_FragColor = czm_phong(normalize(positionToEyeEC), material); - //gl_FragColor = vec4(u, v, 0.0, 1.0); #endif #endif From 2b47810613c0e3c5bc5c419bdb8160a01d38ac2b Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 21 Mar 2018 14:20:21 -0400 Subject: [PATCH 07/40] reduce multifrustum artifacts for EC normals from depth --- .../EllipsoidSurfaceAppearanceFS.glsl | 1 - Source/Shaders/ShadowVolumeColorFS.glsl | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl index 647a5120908..095ea101bd2 100644 --- a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceFS.glsl @@ -30,5 +30,4 @@ void main() #else gl_FragColor = czm_phong(normalize(positionToEyeEC), material); #endif - //gl_FragColor = vec4(positionToEyeEC, 1.0); } diff --git a/Source/Shaders/ShadowVolumeColorFS.glsl b/Source/Shaders/ShadowVolumeColorFS.glsl index d6600295573..ad4c1423cdb 100644 --- a/Source/Shaders/ShadowVolumeColorFS.glsl +++ b/Source/Shaders/ShadowVolumeColorFS.glsl @@ -58,11 +58,28 @@ vec4 getEyeCoord(vec2 fragCoord) { return eyeCoord; } -vec3 getEyeCoord3(vec2 fragCoord) { - vec4 eyeCoord = getEyeCoord(fragCoord); +vec3 getEyeCoord3FromWindowCoord(vec2 fragCoord, float depth) { + vec4 windowCoord = vec4(fragCoord, depth, 1.0); + vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); return eyeCoord.xyz / eyeCoord.w; } +vec3 getVectorFromOffset(vec3 eyeCoord, vec2 fragCoord2, vec2 positiveOffset) { + // Sample depths at both offset and negative offset + float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 + positiveOffset) / czm_viewport.zw)); + float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 - positiveOffset) / czm_viewport.zw)); + + // Explicitly evaluate both paths + bvec2 upOrRightInBounds = lessThan(fragCoord2 + positiveOffset, czm_viewport.zw); + float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y); + float useDownOrLeft = float(useUpOrRight == 0.0); + + vec3 upOrRightEC = getEyeCoord3FromWindowCoord(fragCoord2 + positiveOffset, upOrRightDepth); + vec3 downOrLeftEC = getEyeCoord3FromWindowCoord(fragCoord2 - positiveOffset, downOrLeftDepth); + + return (upOrRightEC - eyeCoord) * useUpOrRight + (eyeCoord - downOrLeftEC) * useDownOrLeft; +} + void main(void) { #ifdef VECTOR_TILE @@ -72,7 +89,8 @@ void main(void) gl_FragColor = v_color; #else - vec4 eyeCoord = getEyeCoord(gl_FragCoord.xy); + vec2 fragCoord2 = gl_FragCoord.xy; + vec4 eyeCoord = getEyeCoord(fragCoord2); vec4 worldCoord4 = czm_inverseView * eyeCoord; vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; @@ -86,19 +104,17 @@ void main(void) /* if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { - discard; + discard; // TODO: re-enable me when Ellipses aren't broken and when batching is necessary }*/ vec3 positionToEyeEC = -(czm_modelView * vec4(worldCoord, 1.0)).xyz; - // compute normal - vec2 fragCoord = gl_FragCoord.xy; + // compute normal. sample adjacent pixels in 2x2 block in screen space float d = 1.0; - - // sample adjacent pixels in 2x2 block in screen space vec3 eyeCoord3 = eyeCoord.xyz / eyeCoord.w; - vec3 downUp = eyeCoord3 - getEyeCoord3(fragCoord - vec2(0.0, d)).xyz; - vec3 leftRight = getEyeCoord3(fragCoord - vec2(d, 0.0)).xyz - eyeCoord3; + vec3 downUp = getVectorFromOffset(eyeCoord3, fragCoord2, vec2(0.0, d)); + vec3 leftRight = getVectorFromOffset(eyeCoord3, fragCoord2, vec2(d, 0.0)); + vec3 normalEC = normalize(cross(downUp, leftRight)); czm_materialInput materialInput; From 693f7e2620a2a6dc0b7757761df9691bcd793c78 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 22 Mar 2018 16:42:35 -0400 Subject: [PATCH 08/40] bespoke shadow volume appearance shaders --- .../gallery/batchingGroundPrims.html | 4 +- Apps/Sandcastle/gallery/testDepth.html | 1 - Source/Scene/ClassificationPrimitive.js | 79 +++-- Source/Scene/GroundPrimitive.js | 23 +- .../createShadowVolumeAppearanceShader.js | 297 ++++++++++++++++++ .../PerInstanceColorAppearanceFS.glsl | 6 +- Source/Shaders/Builtin/Functions/atan2cg.glsl | 42 +++ .../Builtin/Functions/getDefaultMaterial.glsl | 4 +- Source/Shaders/Materials/BumpMapMaterial.glsl | 12 +- Source/Shaders/ShadowVolumeColorFS.glsl | 133 -------- 10 files changed, 423 insertions(+), 178 deletions(-) create mode 100644 Source/Scene/createShadowVolumeAppearanceShader.js create mode 100644 Source/Shaders/Builtin/Functions/atan2cg.glsl delete mode 100644 Source/Shaders/ShadowVolumeColorFS.glsl diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html index c1c711fa099..8a8fa93961b 100644 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -195,8 +195,8 @@ } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); -var rightHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); -rightHandler.setInputAction(function(movement) { +var leftHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); +leftHandler.setInputAction(function(movement) { var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); if (cartesian) { console.log('new Cesium.Cartesian3(' + cartesian.x + ', ' + cartesian.y + ', ' + cartesian.z + '),'); diff --git a/Apps/Sandcastle/gallery/testDepth.html b/Apps/Sandcastle/gallery/testDepth.html index cd08715d737..313bad0b4cf 100644 --- a/Apps/Sandcastle/gallery/testDepth.html +++ b/Apps/Sandcastle/gallery/testDepth.html @@ -123,7 +123,6 @@ goToView(); }); - //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 8c6b002e2e1..1ae74c27446 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -8,18 +8,19 @@ define([ '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/Rectangle', + '../Core/WebGLConstants', '../Core/isArray', '../Renderer/DrawCommand', '../Renderer/Pass', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', - '../Shaders/ShadowVolumeColorFS', '../Shaders/ShadowVolumeFS', '../Shaders/ShadowVolumeVS', '../ThirdParty/when', './BlendingState', './ClassificationType', + './createShadowVolumeAppearanceShader', './DepthFunction', './Material', './MaterialAppearance', @@ -27,8 +28,7 @@ define([ './Primitive', './SceneMode', './StencilFunction', - './StencilOperation', - '../Core/WebGLConstants' + './StencilOperation' ], function( ColorGeometryInstanceAttribute, combine, @@ -39,18 +39,19 @@ define([ DeveloperError, GeometryInstance, Rectangle, + WebGLConstants, isArray, DrawCommand, Pass, RenderState, ShaderProgram, ShaderSource, - ShadowVolumeColorFS, ShadowVolumeFS, ShadowVolumeVS, when, BlendingState, ClassificationType, + createShadowVolumeAppearanceShader, DepthFunction, Material, MaterialAppearance, @@ -58,8 +59,7 @@ define([ Primitive, SceneMode, StencilFunction, - StencilOperation, - WebGLConstants) { + StencilOperation) { 'use strict'; var ClassificationPrimitiveReadOnlyInstanceAttributes = ['color']; @@ -90,7 +90,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. This can either be a single instance or an array of length one. - * @param {Appearance} [options.appearance] The appearance used to render the primitive. + * @param {Appearance} [options.appearance] The appearance used to render the primitive. Defaults to PerInstanceColorAppearance when GeometryInstances have a color attribute. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. @@ -196,32 +196,45 @@ define([ var appearance = options.appearance; - // Require SphericalExtents attribute on all geometries if material isn't PerInstanceColor - if (defined(appearance) && defined(appearance.material) && defined(geometryInstances)) { + var hasPerColorAttribute = false; + var hasSphericalExtentsAttribute = false; var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; - var geometryInstanceCount = geometryInstances.length; + var geometryInstanceCount = geometryInstancesArray.length; for (var i = 0; i < geometryInstanceCount; i++) { - var attributes = geometryInstances[i].attributes; - if (!defined(attributes) || !defined(attributes.sphericalExtents)) { - throw new DeveloperError('Materials on ClassificationPrimitives require sphericalExtents attribute'); + var attributes = geometryInstancesArray[i].attributes; + if (defined(attributes)) { + if (defined(attributes.color)) { + hasPerColorAttribute = true; + } else if (hasPerColorAttribute) { + throw new DeveloperError('All GeometryInstances must have the same attributes.'); } + if (defined(attributes.sphericalExtents)) { + hasSphericalExtentsAttribute = true; + } else if (hasSphericalExtentsAttribute) { + throw new DeveloperError('All GeometryInstances must have the same attributes.'); } + } else if (hasPerColorAttribute || hasSphericalExtentsAttribute) { + throw new DeveloperError('All GeometryInstances must have the same attributes.'); + } } - // If attributes include color and appearance is undefined, then default to a color appearance - if (!defined(appearance)) { - var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; - var geometryInstanceCount = geometryInstances.length; - for (var i = 0; i < geometryInstanceCount; i++) { - var attributes = geometryInstances[i].attributes; - if (defined(attributes) && defined(attributes.color)) { + // If attributes include color and appearance is undefined, default to a color appearance + if (!defined(appearance) && hasPerColorAttribute) { appearance = new PerInstanceColorAppearance({ flat : true }); - break; } + if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { + throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); } + + // TODO: SphericalExtents needed if PerInstanceColor isn't all the same + if (defined(appearance.material) && !hasSphericalExtentsAttribute) { + throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute'); } + + this._hasPerColorAttribute = hasPerColorAttribute; + this._hasSphericalExtentsAttribute = hasSphericalExtentsAttribute; this.appearance = appearance; var readOnlyAttributes; @@ -549,7 +562,7 @@ define([ } } - function createShaderProgram(classificationPrimitive, frameState, appearance) { + function createShaderProgram(classificationPrimitive, frameState) { var context = frameState.context; var primitive = classificationPrimitive._primitive; var vs = ShadowVolumeVS; @@ -635,17 +648,16 @@ define([ }); var parts; + + // Create a fragment shader that computes only required material hookups using screen space techniques + var shadowVolumeAppearanceFS = createShadowVolumeAppearanceShader(appearance); if (isPerInstanceColor) { - parts = [ShadowVolumeColorFS]; + parts = [shadowVolumeAppearanceFS]; } else { - // Modify fsColorSource for material (yay!) - // Only have to modify the FS b/c all material hookups happen in there b/c lol VS - // TODO: scan shaderSource to determine what material inputs are needed for ShadowVolumeColorFS? - parts = [appearance.material.shaderSource, ShadowVolumeColorFS]; + parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS]; } var fsColorSource = new ShaderSource({ - defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : [], sources : [parts.join('\n')] }); @@ -715,7 +727,7 @@ define([ var appearance = classificationPrimitive.appearance; var material = appearance.material; if (defined(material)) { - uniformMap = combine(uniformMap, material._uniforms) + uniformMap = combine(uniformMap, material._uniforms); } command.uniformMap = uniformMap; @@ -1010,7 +1022,16 @@ define([ this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); } // Update primitive appearance + if (this._primitive.appearance !== appearance) { + // Check if the appearance is supported by the geometry attributes + if (!this._hasSphericalExtentsAttribute && defined(appearance.material)) { + throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute'); + } + if (!this._hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { + throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); + } this._primitive.appearance = appearance; + } this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 821169e3dfd..eb1a5b54ef1 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -22,6 +22,7 @@ define([ '../ThirdParty/when', './ClassificationPrimitive', './ClassificationType', + './PerInstanceColorAppearance', './SceneMode' ], function( BoundingSphere, @@ -47,6 +48,7 @@ define([ when, ClassificationPrimitive, ClassificationType, + PerInstanceColorAppearance, SceneMode) { 'use strict'; @@ -72,7 +74,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. - * @param {Appearance} [options.appearance] The appearance used to render the primitive. + * @param {Appearance} [options.appearance] The appearance used to render the primitive. Defaults to PerInstanceColorAppearance when GeometryInstances have a color attribute. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. @@ -134,7 +136,24 @@ define([ function GroundPrimitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this.appearance = options.appearance; + var appearance = options.appearance; + var geometryInstances = options.geometryInstances; + if (!defined(appearance) && defined(geometryInstances)) { + var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; + var geometryInstanceCount = geometryInstancesArray.length; + for (var i = 0; i < geometryInstanceCount; i++) { + var attributes = geometryInstancesArray[i].attributes; + if (defined(attributes) && defined(attributes.color)) { + appearance = new PerInstanceColorAppearance({ + flat : true + }); + break; + } + } + } + + this.appearance = appearance; + /** * The geometry instance rendered with this primitive. This may * be undefined if options.releaseGeometryInstances diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/createShadowVolumeAppearanceShader.js new file mode 100644 index 00000000000..f7c58abf713 --- /dev/null +++ b/Source/Scene/createShadowVolumeAppearanceShader.js @@ -0,0 +1,297 @@ +define([ + '../Core/Check', + '../Core/defaultValue', + '../Core/defineProperties', + '../Renderer/PixelDatatype', + '../Scene/PerInstanceColorAppearance' +], function( + Check, + defaultValue, + defineProperties, + PixelDatatype, + PerInstanceColorAppearance) { + 'use strict'; + + var shaderDependenciesScratch = new ShaderDependencies(); + /** + * Creates the shadow volume fragment shader for a ClassificationPrimitive to use a given appearance. + * + * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive. + * @param {Boolean} [sphericalExtentsCulling=false] Discard fragments outside the instance's spherical extents. + * @returns {String} Shader source for a fragment shader using the input appearance. + * @private + */ + function createShadowVolumeAppearanceShader(appearance, sphericalExtentsCulling) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('appearance', appearance); + //>>includeEnd('debug'); + + var extentsCull = defaultValue(sphericalExtentsCulling, false); + + if (appearance instanceof PerInstanceColorAppearance) { + return getPerInstanceColorShader(extentsCull, appearance.flat); + } + + var shaderDependencies = shaderDependenciesScratch.reset(); + shaderDependencies.requiresTexcoords = extentsCull; + shaderDependencies.requiresEyeCoord = !appearance.flat; + + // Scan material source for what hookups are needed. Assume czm_materialInput materialInput. + var materialShaderSource = appearance.material.shaderSource; + + var usesNormalEC = shaderDependencies.normalEC = materialShaderSource.includes('materialInput.normalEC') || materialShaderSource.includes('czm_getDefaultMaterial'); + var usesPositionToEyeEC = shaderDependencies.positionToEyeEC = materialShaderSource.includes('materialInput.positionToEyeEC'); + var usesTangentToEyeMat = shaderDependencies.tangentToEyeMatrix = materialShaderSource.includes('materialInput.tangentToEyeMatrix'); + var usesSt = shaderDependencies.st = materialShaderSource.includes('materialInput.st'); + + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n'; + if (extentsCull || usesSt) { + glsl += + 'varying vec4 v_sphericalExtents;\n'; + } + + glsl += getLocalFunctions(shaderDependencies); + + glsl += + 'void main(void)\n' + + '{\n'; + + glsl += getDependenciesAndCulling(shaderDependencies, extentsCull); + + glsl += ' czm_materialInput materialInput;\n'; + if (usesNormalEC) { + glsl += ' materialInput.normalEC = normalEC;\n'; + } + if (usesPositionToEyeEC) { + glsl += ' materialInput.positionToEyeEC = -eyeCoord.xyz;\n'; + } + if (usesTangentToEyeMat) { + glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC);\n'; + } + if (usesSt) { + glsl += ' materialInput.st = vec2(v, u);\n'; + } + glsl += ' czm_material material = czm_getMaterial(materialInput);\n'; + + if (appearance.flat) { + glsl += ' gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n'; + } else { + glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoord.xyz), material);\n'; + } + + glsl += '}\n'; + return glsl; + } + + function getPerInstanceColorShader(sphericalExtentsCulling, flatShading) { + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n' + + 'varying vec4 v_color;\n'; + if (sphericalExtentsCulling) { + glsl += + 'varying vec4 v_sphericalExtents;\n'; + } + var shaderDependencies = shaderDependenciesScratch.reset(); + shaderDependencies.requiresTexcoords = sphericalExtentsCulling; + shaderDependencies.requiresNormalEC = !flatShading; + + glsl += getLocalFunctions(shaderDependencies); + + glsl += 'void main(void)\n' + + '{\n'; + + glsl += getDependenciesAndCulling(shaderDependencies, sphericalExtentsCulling); + + if (flatShading) { + glsl += + ' gl_FragColor = v_color;\n'; + } else { + glsl += + ' czm_materialInput materialInput;\n' + + ' materialInput.normalEC = normalEC;\n' + + ' materialInput.positionToEyeEC = -eyeCoord.xyz;\n' + + ' czm_material material = czm_getDefaultMaterial(materialInput);\n' + + ' material.diffuse = v_color.rgb;\n' + + ' material.alpha = v_color.a;\n' + + + ' gl_FragColor = czm_phong(normalize(-eyeCoord.xyz), material);\n'; + } + glsl += '}\n'; + return glsl; + } + + function getDependenciesAndCulling(shaderDependencies, sphericalExtentsCulling) { + var glsl = ''; + if (shaderDependencies.requiresEyeCoord) { + glsl += + ' vec4 eyeCoord = getEyeCoord(gl_FragCoord.xy);\n'; + } + if (shaderDependencies.requiresWorldCoord) { + glsl += + ' vec4 worldCoord4 = czm_inverseView * eyeCoord;\n' + + ' vec3 worldCoord = worldCoord4.xyz / worldCoord4.w;\n'; + } + if (shaderDependencies.requiresTexcoords) { + glsl += + ' // compute sphere normal for spherical coordinates\n' + + ' vec3 sphereNormal = normalize(worldCoord);\n' + + ' // cubic asign approximation\n' + + ' float latitude = ' + approximateAsinForValue('sphereNormal.z') + ';\n' + + ' float longitude = czm_atan2cg(sphereNormal.y, sphereNormal.x);\n' + + ' float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + + ' float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; + } + if (sphericalExtentsCulling) { + glsl += + ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + // TODO: there's floating point problems at the edges of rectangles. Use remapping. + ' discard;\n' + + ' }\n'; + } + // Lots of texture access, so lookup after discard check + if (shaderDependencies.requiresNormalEC) { + glsl += + ' // compute normal. sample adjacent pixels in 2x2 block in screen space\n' + + ' vec3 downUp = getVectorFromOffset(eyeCoord, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + + ' vec3 leftRight = getVectorFromOffset(eyeCoord, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + + ' vec3 normalEC = normalize(cross(leftRight, downUp));\n' + + '\n'; + } + return glsl; + } + + function getLocalFunctions(shaderDependencies) { + var glsl = ''; + if (shaderDependencies.requiresEyeCoord) { + glsl += + 'vec4 getEyeCoord(vec2 fragCoord) {\n' + + ' vec2 coords = fragCoord / czm_viewport.zw;\n' + + ' float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + + ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + + ' vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord);\n' + + ' return eyeCoord;\n' + + '}\n'; + } + if (shaderDependencies.requiresNormalEC) { + glsl += + 'vec3 getEyeCoord3FromWindowCoord(vec2 fragCoord, float depth) {\n' + + ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + + ' vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord);\n' + + ' return eyeCoord.xyz / eyeCoord.w;\n' + + '}\n' + + + 'vec3 getVectorFromOffset(vec4 eyeCoord, vec2 fragCoord2, vec2 positiveOffset) {\n' + + ' // Sample depths at both offset and negative offset\n' + + ' float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 + positiveOffset) / czm_viewport.zw));\n' + + ' float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 - positiveOffset) / czm_viewport.zw));\n' + + // TODO: could re-ordering here help performance? do texture fetches, then logic, then unpack? + ' // Explicitly evaluate both paths\n' + + ' bvec2 upOrRightInBounds = lessThan(fragCoord2 + positiveOffset, czm_viewport.zw);\n' + + ' float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + + ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + + + ' vec3 upOrRightEC = getEyeCoord3FromWindowCoord(fragCoord2 + positiveOffset, upOrRightDepth);\n' + + ' vec3 downOrLeftEC = getEyeCoord3FromWindowCoord(fragCoord2 - positiveOffset, downOrLeftDepth);\n' + + + ' return (upOrRightEC - (eyeCoord.xyz / eyeCoord.w)) * useUpOrRight + ((eyeCoord.xyz / eyeCoord.w) - downOrLeftEC) * useDownOrLeft;\n' + + '}\n'; + } + return glsl; + } + + // Inline-approximate asin(value) using a very rough cubic approximation, (x^3 + x) * PI/4. + // Native glsl asin can vary between vendors, sometimes we need a consistent approximation. + // + // This approximation is relatively inaccurate but does not have first-derivative discontinuity + // for input 0, which is a common problem with sqrt-based approximations that use range reduction to cover [-1, 1]. + // When computing spherical coordinates, such discontinuities cause pinching problems at the equator. + function approximateAsinForValue(valueToken) { + return '(' + valueToken + ' * ' + valueToken + ' * ' + valueToken + ' + ' + valueToken + ') * czm_piOverFour'; + } + + /** + * Tracks shader dependencies. + * @private + */ + function ShaderDependencies() { + this._requiresEyeCoord = false; + this._requiresWorldCoord = false; // depends on eyeCoord, needed for material and for phong + this._requiresNormalEC = false; // depends on eyeCoord, needed for material + this._requiresTexcoords = false; // depends on worldCoord, needed for material and for culling + } + + ShaderDependencies.prototype.reset = function() { + this._requiresEyeCoord = false; + this._requiresWorldCoord = false; + this._requiresNormalEC = false; + this._requiresTexcoords = false; + return this; + }; + + defineProperties(ShaderDependencies.prototype, { + // Set when assessing final shading (flat vs. phong) and spherical extent culling + requiresEyeCoord : { + get : function() { + return this._requiresEyeCoord; + }, + set : function(value) { + this._requiresEyeCoord = value || this._requiresEyeCoord; + } + }, + requiresWorldCoord : { + get : function() { + return this._requiresWorldCoord; + }, + set : function(value) { + this._requiresWorldCoord = value || this._requiresWorldCoord; + this.requiresEyeCoord = this._requiresWorldCoord; + } + }, + requiresNormalEC : { + get : function() { + return this._requiresNormalEC; + }, + set : function(value) { + this._requiresNormalEC = value || this._requiresNormalEC; + this.requiresEyeCoord = this._requiresNormalEC; + } + }, + requiresTexcoords : { + get : function() { + return this._requiresTexcoords; + }, + set : function(value) { + this._requiresTexcoords = value || this._requiresTexcoords; + this.requiresWorldCoord = this._requiresTexcoords; + } + }, + // Set when assessing material hookups + normalEC : { + set : function(value) { + this.requiresNormalEC = value; + } + }, + tangentToEyeMatrix : { + set : function(value) { + this.requiresWorldCoord = value; + this.requiresNormalEC = value; + } + }, + positionToEyeEC : { + set : function(value) { + this.requiresEyeCoord = value; + } + }, + st : { + set : function(value) { + this.requiresTexcoords = value; + } + } + }); + + return createShadowVolumeAppearanceShader; +}); diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl index 6bef5b43865..c2709e847ea 100644 --- a/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl @@ -5,18 +5,18 @@ varying vec4 v_color; void main() { vec3 positionToEyeEC = -v_positionEC; - + vec3 normalEC = normalize(v_normalEC); #ifdef FACE_FORWARD normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif - + czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = positionToEyeEC; czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = v_color.rgb; material.alpha = v_color.a; - + gl_FragColor = czm_phong(normalize(positionToEyeEC), material); } diff --git a/Source/Shaders/Builtin/Functions/atan2cg.glsl b/Source/Shaders/Builtin/Functions/atan2cg.glsl new file mode 100644 index 00000000000..1e130af401d --- /dev/null +++ b/Source/Shaders/Builtin/Functions/atan2cg.glsl @@ -0,0 +1,42 @@ +/** + * Approximates atan2 for inputs y and x. + * Native implementations of atan2 can differ between vendors, so use this approximation for + * computations that require consistency across platforms and between CPU/GPU. + * + * Based on the nvidia cg reference implementation: http://developer.download.nvidia.com/cg/atan2.html + * atan2 is difficult to approximate using identities and other trigonometric approximations because of limited input range. + * + * @name czm_atan2cg + * @glslFunction + * + * @param {float} y A nonzero y-component of a coordinate. + * @param {float} x A nonzero x-component of a coordinate + * + * @returns {float} The floating-point atan2 of y and x + */ +float czm_atan2cg(float y, float x) +{ + float t0, t1, t3, t4; + + t3 = abs(x); + t1 = abs(y); + t0 = max(t3, t1); + t1 = min(t3, t1); + t3 = 1.0 / t0; + t3 = t1 * t3; + + t4 = t3 * t3; + t0 = - 0.013480470; + t0 = t0 * t4 + 0.057477314; + t0 = t0 * t4 - 0.121239071; + t0 = t0 * t4 + 0.195635925; + t0 = t0 * t4 - 0.332994597; + t0 = t0 * t4 + 0.999995630; + t3 = t0 * t3; + + t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; + t3 = (x < 0.0) ? 3.141592654 - t3 : t3; + t3 = (y < 0.0) ? -t3 : t3; + + return t3; +} diff --git a/Source/Shaders/Builtin/Functions/getDefaultMaterial.glsl b/Source/Shaders/Builtin/Functions/getDefaultMaterial.glsl index 409d00bf3e7..14ade737e03 100644 --- a/Source/Shaders/Builtin/Functions/getDefaultMaterial.glsl +++ b/Source/Shaders/Builtin/Functions/getDefaultMaterial.glsl @@ -4,10 +4,10 @@ * The default normal value is given by materialInput.normalEC. * * @name czm_getDefaultMaterial - * @glslFunction + * @glslFunction * * @param {czm_materialInput} input The input used to construct the default material. - * + * * @returns {czm_material} The default material. * * @see czm_materialInput diff --git a/Source/Shaders/Materials/BumpMapMaterial.glsl b/Source/Shaders/Materials/BumpMapMaterial.glsl index 350f6a7c3e8..2316a17fa04 100644 --- a/Source/Shaders/Materials/BumpMapMaterial.glsl +++ b/Source/Shaders/Materials/BumpMapMaterial.glsl @@ -7,23 +7,23 @@ czm_material czm_getMaterial(czm_materialInput materialInput) czm_material material = czm_getDefaultMaterial(materialInput); vec2 st = materialInput.st; - + vec2 centerPixel = fract(repeat * st); float centerBump = texture2D(image, centerPixel).channel; - + float imageWidth = float(imageDimensions.x); vec2 rightPixel = fract(repeat * (st + vec2(1.0 / imageWidth, 0.0))); float rightBump = texture2D(image, rightPixel).channel; - + float imageHeight = float(imageDimensions.y); vec2 leftPixel = fract(repeat * (st + vec2(0.0, 1.0 / imageHeight))); float topBump = texture2D(image, leftPixel).channel; - + vec3 normalTangentSpace = normalize(vec3(centerBump - rightBump, centerBump - topBump, clamp(1.0 - strength, 0.1, 1.0))); vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace; - + material.normal = normalEC; material.diffuse = vec3(0.01); - + return material; } diff --git a/Source/Shaders/ShadowVolumeColorFS.glsl b/Source/Shaders/ShadowVolumeColorFS.glsl deleted file mode 100644 index ad4c1423cdb..00000000000 --- a/Source/Shaders/ShadowVolumeColorFS.glsl +++ /dev/null @@ -1,133 +0,0 @@ -#ifdef GL_EXT_frag_depth -#extension GL_EXT_frag_depth : enable -#endif - -#ifdef VECTOR_TILE -uniform vec4 u_highlightColor; -#else -varying vec4 v_sphericalExtents; -#endif - -#ifdef PER_INSTANCE_COLOR -varying vec4 v_color; -#endif - -float rez = 0.1; - -// http://developer.download.nvidia.com/cg/atan2.html -// Using this instead of identities + approximations of atan, -// because atan approximations usually only work between -1 and 1. -float atan2Ref(float y, float x) -{ - float t0, t1, t3, t4; - - t3 = abs(x); - t1 = abs(y); - t0 = max(t3, t1); - t1 = min(t3, t1); - t3 = 1.0 / t0; - t3 = t1 * t3; - - t4 = t3 * t3; - t0 = - 0.013480470; - t0 = t0 * t4 + 0.057477314; - t0 = t0 * t4 - 0.121239071; - t0 = t0 * t4 + 0.195635925; - t0 = t0 * t4 - 0.332994597; - t0 = t0 * t4 + 0.999995630; - t3 = t0 * t3; - - t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; - t3 = (x < 0.0) ? 3.141592654 - t3 : t3; - t3 = (y < 0.0) ? -t3 : t3; - - return t3; -} - -// kind of inaccurate, but no sucky discontinuities! -float completelyFakeAsin(float x) -{ - return (x * x * x + x) * 0.78539816339; -} - -vec4 getEyeCoord(vec2 fragCoord) { - vec2 coords = fragCoord / czm_viewport.zw; - float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - vec4 windowCoord = vec4(fragCoord, depth, 1.0); - vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); - return eyeCoord; -} - -vec3 getEyeCoord3FromWindowCoord(vec2 fragCoord, float depth) { - vec4 windowCoord = vec4(fragCoord, depth, 1.0); - vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord); - return eyeCoord.xyz / eyeCoord.w; -} - -vec3 getVectorFromOffset(vec3 eyeCoord, vec2 fragCoord2, vec2 positiveOffset) { - // Sample depths at both offset and negative offset - float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 + positiveOffset) / czm_viewport.zw)); - float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 - positiveOffset) / czm_viewport.zw)); - - // Explicitly evaluate both paths - bvec2 upOrRightInBounds = lessThan(fragCoord2 + positiveOffset, czm_viewport.zw); - float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y); - float useDownOrLeft = float(useUpOrRight == 0.0); - - vec3 upOrRightEC = getEyeCoord3FromWindowCoord(fragCoord2 + positiveOffset, upOrRightDepth); - vec3 downOrLeftEC = getEyeCoord3FromWindowCoord(fragCoord2 - positiveOffset, downOrLeftDepth); - - return (upOrRightEC - eyeCoord) * useUpOrRight + (eyeCoord - downOrLeftEC) * useDownOrLeft; -} - -void main(void) -{ -#ifdef VECTOR_TILE - gl_FragColor = u_highlightColor; -#else - #ifdef PER_INSTANCE_COLOR - gl_FragColor = v_color; - #else - - vec2 fragCoord2 = gl_FragCoord.xy; - vec4 eyeCoord = getEyeCoord(fragCoord2); - vec4 worldCoord4 = czm_inverseView * eyeCoord; - vec3 worldCoord = worldCoord4.xyz / worldCoord4.w; - - vec3 sphereNormal = normalize(worldCoord); - - float latitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella - float longitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep - - float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w; - float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z; - - /* - if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { - discard; // TODO: re-enable me when Ellipses aren't broken and when batching is necessary - }*/ - - vec3 positionToEyeEC = -(czm_modelView * vec4(worldCoord, 1.0)).xyz; - - // compute normal. sample adjacent pixels in 2x2 block in screen space - float d = 1.0; - vec3 eyeCoord3 = eyeCoord.xyz / eyeCoord.w; - vec3 downUp = getVectorFromOffset(eyeCoord3, fragCoord2, vec2(0.0, d)); - vec3 leftRight = getVectorFromOffset(eyeCoord3, fragCoord2, vec2(d, 0.0)); - - vec3 normalEC = normalize(cross(downUp, leftRight)); - - czm_materialInput materialInput; - materialInput.normalEC = normalEC; - materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC); - materialInput.positionToEyeEC = positionToEyeEC; - materialInput.st.x = v; - materialInput.st.y = u; - czm_material material = czm_getMaterial(materialInput); - - gl_FragColor = czm_phong(normalize(positionToEyeEC), material); - #endif - -#endif - czm_writeDepthClampedToFarPlane(); -} From f9ca2604604a8b5a9750ba18fd5601443308a0bb Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 23 Mar 2018 17:06:41 -0400 Subject: [PATCH 09/40] switch spherical coordinates computation to atan --- LICENSE.md | 14 ++++ Source/Core/Math.js | 16 ++++ ...hericalExtentsGeometryInstanceAttribute.js | 54 ++++++------- .../createShadowVolumeAppearanceShader.js | 18 +---- .../approximateSphericalCoordinates.glsl | 81 +++++++++++++++++++ Source/Shaders/Builtin/Functions/atan2cg.glsl | 42 ---------- Specs/Core/MathSpec.js | 6 ++ 7 files changed, 145 insertions(+), 86 deletions(-) create mode 100644 Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl delete mode 100644 Source/Shaders/Builtin/Functions/atan2cg.glsl diff --git a/LICENSE.md b/LICENSE.md index 7251c3dfd40..b2e0714950f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -670,6 +670,20 @@ https://github.com/KhronosGroup/glTF-WebGL-PBR >CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE >OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +### ShaderFastLibs (adapted code) + +https://github.com/michaldrobot/ShaderFastLibs + +> The MIT License (MIT) +> +> Copyright (c) <2014> +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + Tests ===== diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 04631d63609..0a482db5a72 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -857,5 +857,21 @@ define([ return 1.0 - Math.exp(-(scalar * scalar)); }; + /** + * Computes a fast approximation of Atan for input in the range [-1, 1]. + * + * Based on Michal Drobot's approximation from ShaderFastLibs, + * which in turn is based on "Efficient approximations for the arctangent function," + * Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006. + * Adapted from ShaderFastLibs under MIT License. + * + * @param {Number} x An input number in the range [-1, 1] + * @returns {Number} An approximation of atan(x) + * @private + */ + CesiumMath.fastApproximateAtan = function(x) { + return x * (-0.1784 * Math.abs(x) - 0.0663 * x * x + 1.0301); + } + return CesiumMath; }); diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js index b450eefcfb0..ede903c7953 100644 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -2,6 +2,7 @@ define([ './Cartesian2', './Cartesian3', './Cartographic', + './Math', './ComponentDatatype', './defineProperties', './Ellipsoid' @@ -9,40 +10,35 @@ define([ Cartesian2, Cartesian3, Cartographic, + CesiumMath, ComponentDatatype, defineProperties, Ellipsoid) { 'use strict'; - function completelyFakeAsin(x) - { - return (x * x * x + x) * 0.78539816339; + function approximateSphericalLatitude(normal) { + var normalZ = normal.z; + var magXY = Math.sqrt(normal.x * normal.x + normal.y * normal.y); + var q = normalZ / magXY; + if (Math.abs(q) < 1.0) { + return CesiumMath.fastApproximateAtan(normalZ / magXY); + } else { + return CesiumMath.sign(normalZ) * CesiumMath.PI_OVER_TWO - CesiumMath.fastApproximateAtan(magXY / normalZ); + } } - function atan2Ref(y, x) { - var t0, t1, t3, t4; - - t3 = Math.abs(x); - t1 = Math.abs(y); - t0 = Math.max(t3, t1); - t1 = Math.min(t3, t1); - t3 = 1.0 / t0; - t3 = t1 * t3; - - t4 = t3 * t3; - t0 = - 0.013480470; - t0 = t0 * t4 + 0.057477314; - t0 = t0 * t4 - 0.121239071; - t0 = t0 * t4 + 0.195635925; - t0 = t0 * t4 - 0.332994597; - t0 = t0 * t4 + 0.999995630; - t3 = t0 * t3; - - t3 = (Math.abs(y) > Math.abs(x)) ? 1.570796327 - t3 : t3; - t3 = (x < 0) ? 3.141592654 - t3 : t3; - t3 = (y < 0) ? -t3 : t3; - - return t3; + function approximateSphericalLongitude(normal) { + var magXY = Math.sqrt(normal.x * normal.x + normal.y * normal.y); + var x = normal.x / magXY; + var y = normal.y / magXY; + if (Math.abs(y / x) < 1.0) { + if (x < 0.0) { + return CesiumMath.sign(y) * CesiumMath.PI - CesiumMath.fastApproximateAtan(y / Math.abs(x)); + } + return CesiumMath.fastApproximateAtan(y / x); + } else { + return CesiumMath.sign(y) * CesiumMath.PI_OVER_TWO - CesiumMath.fastApproximateAtan(x / y); + } } var cartographicScratch = new Cartographic(); @@ -56,8 +52,8 @@ define([ var cartesian = Cartographic.toCartesian(carto, Ellipsoid.WGS84, cartesian3Scratch); var sphereNormal = Cartesian3.normalize(cartesian, cartesian); - var sphereLatitude = completelyFakeAsin(sphereNormal.z); // find a dress for the ball Sinderella - var sphereLongitude = atan2Ref(sphereNormal.y, sphereNormal.x); // the kitTans weep + var sphereLatitude = approximateSphericalLatitude(sphereNormal); + var sphereLongitude = approximateSphericalLongitude(sphereNormal); result.x = sphereLatitude; result.y = sphereLongitude; diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/createShadowVolumeAppearanceShader.js index f7c58abf713..5475515c6f8 100644 --- a/Source/Scene/createShadowVolumeAppearanceShader.js +++ b/Source/Scene/createShadowVolumeAppearanceShader.js @@ -140,11 +140,9 @@ define([ glsl += ' // compute sphere normal for spherical coordinates\n' + ' vec3 sphereNormal = normalize(worldCoord);\n' + - ' // cubic asign approximation\n' + - ' float latitude = ' + approximateAsinForValue('sphereNormal.z') + ';\n' + - ' float longitude = czm_atan2cg(sphereNormal.y, sphereNormal.x);\n' + - ' float u = (latitude - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + - ' float v = (longitude - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; + ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(sphereNormal);\n' + + ' float u = (sphericalLatLong.x - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + + ' float v = (sphericalLatLong.y - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; } if (sphericalExtentsCulling) { glsl += @@ -203,16 +201,6 @@ define([ return glsl; } - // Inline-approximate asin(value) using a very rough cubic approximation, (x^3 + x) * PI/4. - // Native glsl asin can vary between vendors, sometimes we need a consistent approximation. - // - // This approximation is relatively inaccurate but does not have first-derivative discontinuity - // for input 0, which is a common problem with sqrt-based approximations that use range reduction to cover [-1, 1]. - // When computing spherical coordinates, such discontinuities cause pinching problems at the equator. - function approximateAsinForValue(valueToken) { - return '(' + valueToken + ' * ' + valueToken + ' * ' + valueToken + ' + ' + valueToken + ') * czm_piOverFour'; - } - /** * Tracks shader dependencies. * @private diff --git a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl new file mode 100644 index 00000000000..a7d6aaa399f --- /dev/null +++ b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl @@ -0,0 +1,81 @@ +// Based on Michal Drobot's approximation from ShaderFastLibs, which in turn is based on +// "Efficient approximations for the arctangent function," Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006. +// Adapted from ShaderFastLibs under MIT License. +// +// Chosen for the following characteristics over range [-1, 1]: +// - basically no error at -1 and 1, important for getting around range limit (atan2 via atan requires infinite range atan) +// - no need for function mirroring due to range reduction (neede when only approximating [0, 1]) +// - no visible artifacts from first-derivative discontinuities, unlike latitude via range-reduced sqrt asin approximations (at equator) +float fastApproximateAtan(float x) { + return x * (-0.1784 * abs(x) - 0.0663 * x * x + 1.0301); +} + +// Compute longitude using an approximate Atan function +// Because our Atan approximation doesn't have infinite range, +// need to "flip and offset" the result periodically. +float longitudeApproximateAtan(vec2 xy) { + float inAtanBounds = float(abs(xy.y / xy.x) < 1.0); + float outAtanBounds = float(inAtanBounds == 0.0); + float q = inAtanBounds * (xy.y / (sign(xy.x) * xy.x)) + outAtanBounds * (xy.x / xy.y); + + return inAtanBounds * (abs(min(sign(xy.x), 0.0)) * (sign(xy.y) * czm_pi)) + outAtanBounds * (sign(xy.y) * czm_piOverTwo) + + (inAtanBounds * sign(xy.x) - outAtanBounds) * fastApproximateAtan(q); + + /* branch equivalent: */ + //if (abs(xy.y / xy.x) < 1.0) { + // float signX = sign(xy.x); + // return abs(min(signX, 0.0)) * (sign(xy.y) * czm_pi) + signX * fastApproximateAtan(xy.y / (signX * xy.x)); + // /* branch equivalent: */ + // //if (xy.x < 0.0) { + // // return sign(xy.y) * czm_pi - fastApproximateAtan(xy.y / abs(xy.x)); + // //} + // return fastApproximateAtan(xy.y / xy.x); + //} else { + // return sign(xy.y) * czm_piOverTwo - fastApproximateAtan(xy.x / xy.y); + //} +} + +// Compute latitude using an approximate Atan function. +// Because our Atan approximation doesn't have infinite range, +// need to "flip and offset" the result when the vector passes 45 degrees offset from equator. +// Consider: +// atan(2 / 1) == pi/2 - atan(1 / 2) +// atan(2 / -1) == -pi/2 - atan(1 / -2) +// Using atan instead of asin because most asin approximations (and even some vendor implementations!) +// are based on range reduction and sqrt, which causes first-derivative discontuinity and pinching at the equator. +float latitudeApproximateAtan(float magXY, float normalZ) { + float inAtanBounds = float(abs(normalZ / magXY) < 1.0); + float outAtanBounds = float(inAtanBounds == 0.0); + float q = inAtanBounds * (normalZ / magXY) + outAtanBounds * (magXY / normalZ); + return outAtanBounds * sign(normalZ) * czm_piOverTwo + (inAtanBounds - outAtanBounds) * fastApproximateAtan(q); + + /* branch equivalent: */ + //float q = normalZ / magXY; + //if (abs(q) < 1.0) { + // return fastApproximateAtan(normalZ / magXY); + //} else { + // return sign(normalZ) * czm_piOverTwo - fastApproximateAtan(magXY / normalZ); + //} +} + +/** + * Approximately computes spherical coordinates given a normal. + * Uses approximate inverse trigonometry for speed and consistency, + * since inverse trigonometry can differ from vendor-to-vendor and when compared with the CPU. + * + * @name czm_approximateSphericalCoordinates + * @glslFunction + * + * @param {vec3} normal Unit-length normal. + * + * @returns {vec2} Approximate latitude and longitude spherical coordinates. + */ +vec2 czm_approximateSphericalCoordinates(vec3 normal) { + // Project into plane with vertical for latitude + float magXY = sqrt(normal.x * normal.x + normal.y * normal.y); + + // Project into equatorial plane for longitude + vec2 xy = normal.xy / magXY; + + return vec2(latitudeApproximateAtan(magXY, normal.z), longitudeApproximateAtan(xy)); +} diff --git a/Source/Shaders/Builtin/Functions/atan2cg.glsl b/Source/Shaders/Builtin/Functions/atan2cg.glsl deleted file mode 100644 index 1e130af401d..00000000000 --- a/Source/Shaders/Builtin/Functions/atan2cg.glsl +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Approximates atan2 for inputs y and x. - * Native implementations of atan2 can differ between vendors, so use this approximation for - * computations that require consistency across platforms and between CPU/GPU. - * - * Based on the nvidia cg reference implementation: http://developer.download.nvidia.com/cg/atan2.html - * atan2 is difficult to approximate using identities and other trigonometric approximations because of limited input range. - * - * @name czm_atan2cg - * @glslFunction - * - * @param {float} y A nonzero y-component of a coordinate. - * @param {float} x A nonzero x-component of a coordinate - * - * @returns {float} The floating-point atan2 of y and x - */ -float czm_atan2cg(float y, float x) -{ - float t0, t1, t3, t4; - - t3 = abs(x); - t1 = abs(y); - t0 = max(t3, t1); - t1 = min(t3, t1); - t3 = 1.0 / t0; - t3 = t1 * t3; - - t4 = t3 * t3; - t0 = - 0.013480470; - t0 = t0 * t4 + 0.057477314; - t0 = t0 * t4 - 0.121239071; - t0 = t0 * t4 + 0.195635925; - t0 = t0 * t4 - 0.332994597; - t0 = t0 * t4 + 0.999995630; - t3 = t0 * t3; - - t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3; - t3 = (x < 0.0) ? 3.141592654 - t3 : t3; - t3 = (y < 0.0) ? -t3 : t3; - - return t3; -} diff --git a/Specs/Core/MathSpec.js b/Specs/Core/MathSpec.js index 26a4e1b523e..ff75ba27569 100644 --- a/Specs/Core/MathSpec.js +++ b/Specs/Core/MathSpec.js @@ -442,4 +442,10 @@ defineSuite([ expect(CesiumMath.cbrt(1.0)).toEqual(1.0); expect(CesiumMath.cbrt()).toEqual(NaN); }); + + it('fastApproximateAtan', function() { + expect(CesiumMath.fastApproximateAtan(0.0)).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(CesiumMath.fastApproximateAtan(1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON7); + expect(CesiumMath.fastApproximateAtan(-1.0)).toEqualEpsilon(-CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON7); + }); }); From d02346b46bc9f16033acad11b6e399ddd9ef434b Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 26 Mar 2018 10:38:19 -0400 Subject: [PATCH 10/40] simplified spherical coordinates via atan --- Source/Core/Math.js | 42 +++++++-- ...hericalExtentsGeometryInstanceAttribute.js | 33 ++----- .../createShadowVolumeAppearanceShader.js | 5 +- .../approximateSphericalCoordinates.glsl | 87 +++++++------------ Specs/Core/MathSpec.js | 12 ++- Specs/Renderer/BuiltinFunctionsSpec.js | 11 +++ 6 files changed, 97 insertions(+), 93 deletions(-) diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 0a482db5a72..e26db0547a5 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -265,10 +265,10 @@ define([ * @returns {Number} The hyperbolic sine of value. */ CesiumMath.sinh = function(value) { - var part1 = Math.pow(Math.E, value); - var part2 = Math.pow(Math.E, -1.0 * value); + var paropposite = Math.pow(Math.E, value); + var part = Math.pow(Math.E, -1.0 * value); - return (part1 - part2) * 0.5; + return (paropposite - part) * 0.5; }; /** @@ -291,10 +291,10 @@ define([ * @returns {Number} The hyperbolic cosine of value. */ CesiumMath.cosh = function(value) { - var part1 = Math.pow(Math.E, value); - var part2 = Math.pow(Math.E, -1.0 * value); + var paropposite = Math.pow(Math.E, value); + var part = Math.pow(Math.E, -1.0 * value); - return (part1 + part2) * 0.5; + return (paropposite + part) * 0.5; }; /** @@ -871,7 +871,35 @@ define([ */ CesiumMath.fastApproximateAtan = function(x) { return x * (-0.1784 * Math.abs(x) - 0.0663 * x * x + 1.0301); - } + }; + + /** + * Computes a fast approximation of Atan2(x, y) for arbitrary input scalars. + * + * Range reduction math based on nvidia's cg reference implementation: http://developer.download.nvidia.com/cg/atan2.html + * + * @param {Number} x An input number that isn't zero if y is zero. + * @param {Number} y An input number that isn't zero if x is zero. + * @returns {Number} An approximation of atan2(x, y) + * @private + */ + CesiumMath.fastApproximateAtan2 = function(x, y) { + // atan approximations are usually only reliable over [-1, 1] + // So reduce the range by flipping whether x or y is on top. + var opposite, adjacent, t; // t used as swap and atan result. + t = Math.abs(x); + opposite = Math.abs(y); + adjacent = Math.max(t, opposite); + opposite = Math.min(t, opposite); + + t = CesiumMath.fastApproximateAtan(opposite / adjacent); + + // Undo range reduction + t = Math.abs(y) > Math.abs(x) ? CesiumMath.PI_OVER_TWO - t : t; + t = x < 0.0 ? CesiumMath.PI - t : t; + t = y < 0.0 ? -t : t; + return t; + }; return CesiumMath; }); diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js index ede903c7953..38ae807a88b 100644 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -16,29 +16,14 @@ define([ Ellipsoid) { 'use strict'; - function approximateSphericalLatitude(normal) { - var normalZ = normal.z; - var magXY = Math.sqrt(normal.x * normal.x + normal.y * normal.y); - var q = normalZ / magXY; - if (Math.abs(q) < 1.0) { - return CesiumMath.fastApproximateAtan(normalZ / magXY); - } else { - return CesiumMath.sign(normalZ) * CesiumMath.PI_OVER_TWO - CesiumMath.fastApproximateAtan(magXY / normalZ); - } + function approximateSphericalLatitude(spherePoint) { + // Project into plane with vertical for latitude + var magXY = Math.sqrt(spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y); + return CesiumMath.fastApproximateAtan2(magXY, spherePoint.z); } - function approximateSphericalLongitude(normal) { - var magXY = Math.sqrt(normal.x * normal.x + normal.y * normal.y); - var x = normal.x / magXY; - var y = normal.y / magXY; - if (Math.abs(y / x) < 1.0) { - if (x < 0.0) { - return CesiumMath.sign(y) * CesiumMath.PI - CesiumMath.fastApproximateAtan(y / Math.abs(x)); - } - return CesiumMath.fastApproximateAtan(y / x); - } else { - return CesiumMath.sign(y) * CesiumMath.PI_OVER_TWO - CesiumMath.fastApproximateAtan(x / y); - } + function approximateSphericalLongitude(spherePoint) { + return CesiumMath.fastApproximateAtan2(spherePoint.x, spherePoint.y); } var cartographicScratch = new Cartographic(); @@ -50,10 +35,8 @@ define([ carto.height = 0.0; var cartesian = Cartographic.toCartesian(carto, Ellipsoid.WGS84, cartesian3Scratch); - var sphereNormal = Cartesian3.normalize(cartesian, cartesian); - - var sphereLatitude = approximateSphericalLatitude(sphereNormal); - var sphereLongitude = approximateSphericalLongitude(sphereNormal); + var sphereLatitude = approximateSphericalLatitude(cartesian); + var sphereLongitude = approximateSphericalLongitude(cartesian); result.x = sphereLatitude; result.y = sphereLongitude; diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/createShadowVolumeAppearanceShader.js index 5475515c6f8..49b4c22b90a 100644 --- a/Source/Scene/createShadowVolumeAppearanceShader.js +++ b/Source/Scene/createShadowVolumeAppearanceShader.js @@ -138,9 +138,8 @@ define([ } if (shaderDependencies.requiresTexcoords) { glsl += - ' // compute sphere normal for spherical coordinates\n' + - ' vec3 sphereNormal = normalize(worldCoord);\n' + - ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(sphereNormal);\n' + + ' // Treat world coords as a sphere normal for spherical coordinates\n' + + ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoord);\n' + ' float u = (sphericalLatLong.x - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + ' float v = (sphericalLatLong.y - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; } diff --git a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl index a7d6aaa399f..d9d915da0cc 100644 --- a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl @@ -2,60 +2,40 @@ // "Efficient approximations for the arctangent function," Rajan, S. Sichun Wang Inkol, R. Joyal, A., May 2006. // Adapted from ShaderFastLibs under MIT License. // -// Chosen for the following characteristics over range [-1, 1]: -// - basically no error at -1 and 1, important for getting around range limit (atan2 via atan requires infinite range atan) -// - no need for function mirroring due to range reduction (neede when only approximating [0, 1]) +// Chosen for the following characteristics over range [0, 1]: +// - basically no error at 0 and 1, important for getting around range limit (naive atan2 via atan requires infinite range atan) // - no visible artifacts from first-derivative discontinuities, unlike latitude via range-reduced sqrt asin approximations (at equator) -float fastApproximateAtan(float x) { - return x * (-0.1784 * abs(x) - 0.0663 * x * x + 1.0301); +// +// The original code is x * (-0.1784 * abs(x) - 0.0663 * x * x + 1.0301); +// Removed the abs() in here because it isn't needed, the input range is guaranteed as [0, 1] by how we're approximating atan2. +float fastApproximateAtan01(float x) { + return x * (-0.1784 * x - 0.0663 * x * x + 1.0301); } -// Compute longitude using an approximate Atan function -// Because our Atan approximation doesn't have infinite range, -// need to "flip and offset" the result periodically. -float longitudeApproximateAtan(vec2 xy) { - float inAtanBounds = float(abs(xy.y / xy.x) < 1.0); - float outAtanBounds = float(inAtanBounds == 0.0); - float q = inAtanBounds * (xy.y / (sign(xy.x) * xy.x)) + outAtanBounds * (xy.x / xy.y); - - return inAtanBounds * (abs(min(sign(xy.x), 0.0)) * (sign(xy.y) * czm_pi)) + outAtanBounds * (sign(xy.y) * czm_piOverTwo) + - (inAtanBounds * sign(xy.x) - outAtanBounds) * fastApproximateAtan(q); - - /* branch equivalent: */ - //if (abs(xy.y / xy.x) < 1.0) { - // float signX = sign(xy.x); - // return abs(min(signX, 0.0)) * (sign(xy.y) * czm_pi) + signX * fastApproximateAtan(xy.y / (signX * xy.x)); - // /* branch equivalent: */ - // //if (xy.x < 0.0) { - // // return sign(xy.y) * czm_pi - fastApproximateAtan(xy.y / abs(xy.x)); - // //} - // return fastApproximateAtan(xy.y / xy.x); - //} else { - // return sign(xy.y) * czm_piOverTwo - fastApproximateAtan(xy.x / xy.y); - //} +// Used in place of ternaries to trade some math for branching +float branchFree(bool comparison, float a, float b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); } -// Compute latitude using an approximate Atan function. -// Because our Atan approximation doesn't have infinite range, -// need to "flip and offset" the result when the vector passes 45 degrees offset from equator. -// Consider: -// atan(2 / 1) == pi/2 - atan(1 / 2) -// atan(2 / -1) == -pi/2 - atan(1 / -2) -// Using atan instead of asin because most asin approximations (and even some vendor implementations!) -// are based on range reduction and sqrt, which causes first-derivative discontuinity and pinching at the equator. -float latitudeApproximateAtan(float magXY, float normalZ) { - float inAtanBounds = float(abs(normalZ / magXY) < 1.0); - float outAtanBounds = float(inAtanBounds == 0.0); - float q = inAtanBounds * (normalZ / magXY) + outAtanBounds * (magXY / normalZ); - return outAtanBounds * sign(normalZ) * czm_piOverTwo + (inAtanBounds - outAtanBounds) * fastApproximateAtan(q); - - /* branch equivalent: */ - //float q = normalZ / magXY; - //if (abs(q) < 1.0) { - // return fastApproximateAtan(normalZ / magXY); - //} else { - // return sign(normalZ) * czm_piOverTwo - fastApproximateAtan(magXY / normalZ); - //} +// Range reduction math based on nvidia's cg reference implementation for atan2: http://developer.download.nvidia.com/cg/atan2.html +// However, we replaced their atan curve with Michael Drobot's. +float fastApproximateAtan2(float x, float y) { + // atan approximations are usually only reliable over [-1, 1], or, in our case, [0, 1] due to modifications. + // So range-reduce using abs and by flipping whether x or y is on top. + float opposite, adjacent, t; // t used as swap and atan result. + t = abs(x); + opposite = abs(y); + adjacent = max(t, opposite); + opposite = min(t, opposite); + + t = fastApproximateAtan01(opposite / adjacent); + + // Undo range reduction + t = branchFree(abs(y) > abs(x), czm_piOverTwo - t, t); + t = branchFree(x < 0.0, czm_pi - t, t); + t = branchFree(y < 0.0, -t, t); + return t; } /** @@ -72,10 +52,7 @@ float latitudeApproximateAtan(float magXY, float normalZ) { */ vec2 czm_approximateSphericalCoordinates(vec3 normal) { // Project into plane with vertical for latitude - float magXY = sqrt(normal.x * normal.x + normal.y * normal.y); - - // Project into equatorial plane for longitude - vec2 xy = normal.xy / magXY; - - return vec2(latitudeApproximateAtan(magXY, normal.z), longitudeApproximateAtan(xy)); + float latitudeApproximation = fastApproximateAtan2(sqrt(normal.x * normal.x + normal.y * normal.y), normal.z); + float longitudeApproximation = fastApproximateAtan2(normal.x, normal.y); + return vec2(latitudeApproximation, longitudeApproximation); } diff --git a/Specs/Core/MathSpec.js b/Specs/Core/MathSpec.js index ff75ba27569..47761187e8e 100644 --- a/Specs/Core/MathSpec.js +++ b/Specs/Core/MathSpec.js @@ -444,8 +444,14 @@ defineSuite([ }); it('fastApproximateAtan', function() { - expect(CesiumMath.fastApproximateAtan(0.0)).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(CesiumMath.fastApproximateAtan(1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON7); - expect(CesiumMath.fastApproximateAtan(-1.0)).toEqualEpsilon(-CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON7); + expect(CesiumMath.fastApproximateAtan(0.0)).toEqualEpsilon(0.0, CesiumMath.EPSILON3); + expect(CesiumMath.fastApproximateAtan(1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON3); + expect(CesiumMath.fastApproximateAtan(-1.0)).toEqualEpsilon(-CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON3); + }); + + it('fastApproximateAtan2', function() { + expect(CesiumMath.fastApproximateAtan2(1.0, 0.0)).toEqualEpsilon(0.0, CesiumMath.EPSILON3); + expect(CesiumMath.fastApproximateAtan2(1.0, 1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON3); + expect(CesiumMath.fastApproximateAtan2(-1.0, 1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR + CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON3); }); }); diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index b1e80ffad5a..8d4e31bc613 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -366,4 +366,15 @@ defineSuite([ fragmentShader : fs }).contextToRender(); }); + + it('has czm_approximateSphericalCoordinates', function() { + var fs = + 'void main() { ' + + ' gl_FragColor = vec4(all(equal(czm_approximateSphericalCoordinates(vec3(1.0, 0.0, 0.0)), vec2(0.0, 0.0))));' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); }, 'WebGL'); From e7c4679e4bdc0b984873293785a3399d78fa5705 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 28 Mar 2018 15:52:09 -0400 Subject: [PATCH 11/40] added plane-based texcoords, still has precision artifacts --- .../gallery/batchingGroundPrims.html | 8 +- .../PlanarExtentsGeometryInstanceAttribute.js | 193 ++++++++++++++++++ ...hericalExtentsGeometryInstanceAttribute.js | 40 +++- Source/Scene/GroundPrimitive.js | 10 +- .../createShadowVolumeAppearanceShader.js | 51 +++-- .../approximateSphericalCoordinates.glsl | 2 +- Source/Shaders/ShadowVolumeVS.glsl | 4 + 7 files changed, 276 insertions(+), 32 deletions(-) create mode 100644 Source/Core/PlanarExtentsGeometryInstanceAttribute.js diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html index 8a8fa93961b..dccf66dbad4 100644 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -168,9 +168,11 @@ } }); */ + var rightHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); rightHandler.setInputAction(function(movement) { - var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); + var cartesian = viewer.scene.pickPosition(movement.position); + //var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); if (cartesian) { var cartographic = Cesium.Cartographic.fromCartesian(cartesian); var lat = Cesium.Math.toDegrees(cartographic.latitude); @@ -183,7 +185,7 @@ viewer.entities.add({ name : lat + ' ' + long, rectangle : { - coordinates : Cesium.Rectangle.fromDegrees(long - 0.2, lat - 0.1, long + 0.2, lat + 0.1), + coordinates : Cesium.Rectangle.fromDegrees(long - 0.0002, lat - 0.0001, long + 0.0002, lat + 0.0001), material : new Cesium.CheckerboardMaterialProperty({ evenColor : Cesium.Color.ORANGE, @@ -197,7 +199,7 @@ var leftHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); leftHandler.setInputAction(function(movement) { - var cartesian = viewer.camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); + var cartesian = viewer.scene.pickPosition(movement.position); if (cartesian) { console.log('new Cesium.Cartesian3(' + cartesian.x + ', ' + cartesian.y + ', ' + cartesian.z + '),'); } diff --git a/Source/Core/PlanarExtentsGeometryInstanceAttribute.js b/Source/Core/PlanarExtentsGeometryInstanceAttribute.js new file mode 100644 index 00000000000..e0b8f257de2 --- /dev/null +++ b/Source/Core/PlanarExtentsGeometryInstanceAttribute.js @@ -0,0 +1,193 @@ +define([ + './Cartesian2', + './Cartesian3', + './Cartographic', + './Math', + './Check', + './ComponentDatatype', + './defineProperties', + './Ellipsoid', + './Plane' + ], function( + Cartesian2, + Cartesian3, + Cartographic, + CesiumMath, + Check, + ComponentDatatype, + defineProperties, + Ellipsoid, + Plane) { + 'use strict'; + + var forwardScratch = new Cartesian3(); + var normalScratch = new Cartesian3(); + var upScratch = new Cartesian3(); + var rightScratch = new Cartesian3(); + var planeScratch = new Plane(Cartesian3.UNIT_X, 0.0); + /** + * Plane extents needed when computing ground primitive texture coordinates per-instance. + * Used for "small distances." + * Consists of a normal of magnitude range and a distance. + * + * @alias PlanarExtentsGeometryInstanceAttribute + * @constructor + * + * @param {Cartesian3} rectangle Conservative bounding rectangle around the instance. + * @param {Cartesian3} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + * @see createShadowVolumeAppearanceShader + * @private + */ + function PlanarExtentsGeometryInstanceAttribute(origin, end) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('origin', origin); + Check.typeOf.object('end', end); + //>>includeEnd('debug'); + + var forward = Cartesian3.subtract(end, origin, forwardScratch); + forward = Cartesian3.normalize(forward, forward); + var up = Cartesian3.normalize(origin, upScratch); + var right = Cartesian3.cross(up, forward, rightScratch); + + var normal = Cartesian3.cross(up, right, normalScratch); + + var plane = planeScratch; + plane.normal = normal; + plane.distance = 0.0; + var planeDistance = Plane.getPointDistance(plane, end); + + this.value = new Float32Array([normal.x, normal.y, normal.z, 1.0 / planeDistance]); + } + + defineProperties(PlanarExtentsGeometryInstanceAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link PlanarExtentsGeometryInstanceAttribute#value}. + * + * @memberof PlanarExtentsGeometryInstanceAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link PlanarExtentsGeometryInstanceAttribute#value}. + * + * @memberof PlanarExtentsGeometryInstanceAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 4 + */ + componentsPerAttribute : { + get : function() { + return 4; + } + }, + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @memberof PlanarExtentsGeometryInstanceAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + var cartographicScratch = new Cartographic(); + var northMiddleScratch = new Cartesian3(); + var southMiddleScratch = new Cartesian3(); + /** + * Plane extents needed when computing ground primitive texture coordinates per-instance in the latitude direction. + * Used for "small distances." + * Consists of an oct-32 encoded normal packed to a float, a float distance, and a float range + * + * @alias PlanarExtentsGeometryInstanceAttribute + * @constructor + * + * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. + * @param {Ellipoid} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + * @see createShadowVolumeAppearanceShader + * @private + */ + PlanarExtentsGeometryInstanceAttribute.getLatitudeExtents = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + //>>includeEnd('debug'); + + var cartographic = cartographicScratch; + + cartographic.latitude = rectangle.north; + cartographic.longitude = (rectangle.east + rectangle.west) * 0.5; + var northMiddle = Cartographic.toCartesian(cartographic, ellipsoid, northMiddleScratch); + + cartographic.latitude = rectangle.south; + var southMiddle = Cartographic.toCartesian(cartographic, ellipsoid, southMiddleScratch); + + return new PlanarExtentsGeometryInstanceAttribute(southMiddle, northMiddle); + } + + var eastMiddleScratch = new Cartesian3(); + var westMiddleScratch = new Cartesian3(); + + /** + * Plane extents needed when computing ground primitive texture coordinates per-instance in the longitude direction. + * Used for "small distances." + * Consists of an oct-32 encoded normal packed to a float, a float distance, and a float range + * + * @alias PlanarExtentsGeometryInstanceAttribute + * @constructor + * + * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. + * @param {Ellipoid} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + * @see createShadowVolumeAppearanceShader + * @private + */ + PlanarExtentsGeometryInstanceAttribute.getLongitudeExtents = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + //>>includeEnd('debug'); + + var cartographic = cartographicScratch; + + cartographic.latitude = (rectangle.north + rectangle.south) * 0.5; + cartographic.longitude = rectangle.east; + var eastMiddle = Cartographic.toCartesian(cartographic, ellipsoid, eastMiddleScratch); + + cartographic.longitude = rectangle.west; + var westMiddle = Cartographic.toCartesian(cartographic, ellipsoid, westMiddleScratch); + + return new PlanarExtentsGeometryInstanceAttribute(westMiddle, eastMiddle); + } + + return PlanarExtentsGeometryInstanceAttribute; +}); diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js index 38ae807a88b..796c1691d0a 100644 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js @@ -2,6 +2,7 @@ define([ './Cartesian2', './Cartesian3', './Cartographic', + './Check', './Math', './ComponentDatatype', './defineProperties', @@ -10,6 +11,7 @@ define([ Cartesian2, Cartesian3, Cartographic, + Check, CesiumMath, ComponentDatatype, defineProperties, @@ -44,24 +46,44 @@ define([ } var sphericalScratch = new Cartesian2(); + + /** + * Spherical extents needed when computing ground primitive texture coordinates per-instance. + * Used for "large distances." + * Computation is matched to in-shader approximations. + * + * Consists of western and southern spherical coordinates and inverse ranges. + * + * @alias SphericalExtentsGeometryInstanceAttribute + * @constructor + * + * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. + * + * @see GeometryInstance + * @see GeometryInstanceAttribute + * @see createShadowVolumeAppearanceShader + * @private + */ function SphericalExtentsGeometryInstanceAttribute(rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + // rectangle cartographic coords !== spherical because it's on an ellipsoid var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, sphericalScratch); // Slightly pad extents to avoid floating point error when fragment culling at edges. - // TODO: what's the best value for this? - // TODO: should we undo this in the shader? - var south = southWestExtents.x - 0.00001; - var west = southWestExtents.y - 0.00001; + var south = southWestExtents.x - CesiumMath.EPSILON5; + var west = southWestExtents.y - CesiumMath.EPSILON5; var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, sphericalScratch); - var north = northEastExtents.x + 0.00001; - var east = northEastExtents.y + 0.00001; + var north = northEastExtents.x + CesiumMath.EPSILON5; + var east = northEastExtents.y + CesiumMath.EPSILON5; - var longitudeRange = 1.0 / (east - west); - var latitudeRange = 1.0 / (north - south); + var longitudeRangeInverse = 1.0 / (east - west); + var latitudeRangeInverse = 1.0 / (north - south); - this.value = new Float32Array([west, south, longitudeRange, latitudeRange]); + this.value = new Float32Array([west, south, longitudeRangeInverse, latitudeRangeInverse]); } defineProperties(SphericalExtentsGeometryInstanceAttribute.prototype, { diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index a19f277b606..5728934179c 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -15,6 +15,7 @@ define([ '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', + '../Core/PlanarExtentsGeometryInstanceAttribute', '../Core/Rectangle', '../Core/Resource', '../Core/SphericalExtentsGeometryInstanceAttribute', @@ -41,6 +42,7 @@ define([ isArray, CesiumMath, OrientedBoundingBox, + PlanarExtentsGeometryInstanceAttribute, Rectangle, Resource, SphericalExtentsGeometryInstanceAttribute, @@ -767,7 +769,7 @@ define([ } // Now compute the min/max heights for the primitive - setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid); + setMinMaxTerrainHeights(this, rectangle, ellipsoid); var exaggeration = frameState.terrainExaggeration; this._minHeight = this._minTerrainHeight * exaggeration; this._maxHeight = this._maxTerrainHeight * exaggeration; @@ -777,9 +779,13 @@ define([ geometry = instance.geometry; instanceType = geometry.constructor; + var rectangle = getRectangle(frameState, geometry); var attributes = { - sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(getRectangle(frameState, geometry)) + sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(rectangle), + longitudePlaneExtents : PlanarExtentsGeometryInstanceAttribute.getLongitudeExtents(rectangle, ellipsoid), + latitudePlaneExtents : PlanarExtentsGeometryInstanceAttribute.getLatitudeExtents(rectangle, ellipsoid) }; + // TODO: pick and choose? var instanceAttributes = instance.attributes; for (var attributeKey in instanceAttributes) { if (instanceAttributes.hasOwnProperty(attributeKey)) { diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/createShadowVolumeAppearanceShader.js index 49b4c22b90a..daacdbe5e0c 100644 --- a/Source/Scene/createShadowVolumeAppearanceShader.js +++ b/Source/Scene/createShadowVolumeAppearanceShader.js @@ -17,19 +17,20 @@ define([ * Creates the shadow volume fragment shader for a ClassificationPrimitive to use a given appearance. * * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive. - * @param {Boolean} [sphericalExtentsCulling=false] Discard fragments outside the instance's spherical extents. + * @param {Boolean} [extentsCulling=false] Discard fragments outside the instance's spherical extents. * @returns {String} Shader source for a fragment shader using the input appearance. * @private */ - function createShadowVolumeAppearanceShader(appearance, sphericalExtentsCulling) { + function createShadowVolumeAppearanceShader(appearance, extentsCulling, small) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('appearance', appearance); //>>includeEnd('debug'); - var extentsCull = defaultValue(sphericalExtentsCulling, false); + var extentsCull = defaultValue(extentsCulling, true); + var smallExtents = defaultValue(small, true); if (appearance instanceof PerInstanceColorAppearance) { - return getPerInstanceColorShader(extentsCull, appearance.flat); + return getPerInstanceColorShader(extentsCull, appearance.flat, smallExtents); } var shaderDependencies = shaderDependenciesScratch.reset(); @@ -50,16 +51,18 @@ define([ '#endif\n'; if (extentsCull || usesSt) { glsl += - 'varying vec4 v_sphericalExtents;\n'; + 'varying vec4 v_sphericalExtents;\n' + + 'varying vec4 v_planarExtentsLatitude;\n' + + 'varying vec4 v_planarExtentsLongitude;\n'; } - glsl += getLocalFunctions(shaderDependencies); + glsl += getLocalFunctions(shaderDependencies, smallExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCull); + glsl += getDependenciesAndCulling(shaderDependencies, extentsCull, smallExtents); glsl += ' czm_materialInput materialInput;\n'; if (usesNormalEC) { @@ -86,26 +89,26 @@ define([ return glsl; } - function getPerInstanceColorShader(sphericalExtentsCulling, flatShading) { + function getPerInstanceColorShader(extentsCulling, flatShading, smallExtents) { var glsl = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + '#endif\n' + 'varying vec4 v_color;\n'; - if (sphericalExtentsCulling) { + if (extentsCulling) { glsl += 'varying vec4 v_sphericalExtents;\n'; } var shaderDependencies = shaderDependenciesScratch.reset(); - shaderDependencies.requiresTexcoords = sphericalExtentsCulling; + shaderDependencies.requiresTexcoords = extentsCulling; shaderDependencies.requiresNormalEC = !flatShading; - glsl += getLocalFunctions(shaderDependencies); + glsl += getLocalFunctions(shaderDependencies, smallExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, sphericalExtentsCulling); + glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, smallExtents); if (flatShading) { glsl += @@ -125,7 +128,7 @@ define([ return glsl; } - function getDependenciesAndCulling(shaderDependencies, sphericalExtentsCulling) { + function getDependenciesAndCulling(shaderDependencies, extentsCulling, smallExtents) { var glsl = ''; if (shaderDependencies.requiresEyeCoord) { glsl += @@ -137,13 +140,20 @@ define([ ' vec3 worldCoord = worldCoord4.xyz / worldCoord4.w;\n'; } if (shaderDependencies.requiresTexcoords) { - glsl += + if (smallExtents) { // TODO: add ability to do long-and-narrows? + glsl += + ' // Unpack planes and transform to eye space\n' + + ' float u = computePlanarTexcoord(v_planarExtentsLatitude, worldCoord);\n' + + ' float v = computePlanarTexcoord(v_planarExtentsLongitude, worldCoord);\n'; + } else { + glsl += ' // Treat world coords as a sphere normal for spherical coordinates\n' + ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoord);\n' + ' float u = (sphericalLatLong.x - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + - ' float v = (sphericalLatLong.y - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; + ' float v = (sphericalLatLong.y - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; // TODO: clean up... + } } - if (sphericalExtentsCulling) { + if (extentsCulling) { glsl += ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + // TODO: there's floating point problems at the edges of rectangles. Use remapping. ' discard;\n' + @@ -161,7 +171,7 @@ define([ return glsl; } - function getLocalFunctions(shaderDependencies) { + function getLocalFunctions(shaderDependencies, smallExtents) { var glsl = ''; if (shaderDependencies.requiresEyeCoord) { glsl += @@ -197,6 +207,13 @@ define([ ' return (upOrRightEC - (eyeCoord.xyz / eyeCoord.w)) * useUpOrRight + ((eyeCoord.xyz / eyeCoord.w) - downOrLeftEC) * useDownOrLeft;\n' + '}\n'; } + if (shaderDependencies.requiresTexcoords && smallExtents) { + glsl += + 'float computePlanarTexcoord(vec4 packedPlanarExtent, vec3 worldCoords) {\n' + + ' // planar extent is a plane at the origin (so just a direction) and an extent distance.\n' + + ' return (dot(packedPlanarExtent.xyz, worldCoords)) * packedPlanarExtent.w;\n' + + '}\n'; + } return glsl; } diff --git a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl index d9d915da0cc..ace9edae9f0 100644 --- a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl @@ -46,7 +46,7 @@ float fastApproximateAtan2(float x, float y) { * @name czm_approximateSphericalCoordinates * @glslFunction * - * @param {vec3} normal Unit-length normal. + * @param {vec3} normal arbitrary-length normal. * * @returns {vec2} Approximate latitude and longitude spherical coordinates. */ diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 7e89b63a114..64f9297ac55 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -18,6 +18,8 @@ uniform float u_globeMinimumAltitude; #ifndef VECTOR_TILE varying vec4 v_sphericalExtents; +varying vec4 v_planarExtentsLatitude; +varying vec4 v_planarExtentsLongitude; #endif #ifdef PER_INSTANCE_COLOR @@ -35,6 +37,8 @@ void main() #endif v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); + v_planarExtentsLatitude = czm_batchTable_latitudePlaneExtents(batchId); + v_planarExtentsLongitude = czm_batchTable_longitudePlaneExtents(batchId); vec4 position = czm_computePosition(); From 23d6dfbfc16c97b7bc81ed47059656ca2ebf4856 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 29 Mar 2018 11:31:59 -0400 Subject: [PATCH 12/40] changed texcoord planes to use a model matrix attribute, less inaccuracy but still jittery --- .../InversePlaneExtentsGeometryAttribute.js | 108 ++++++++++ .../MatrixColumnGeometryInstanceAttribute.js | 87 ++++++++ .../PlanarExtentsGeometryInstanceAttribute.js | 193 ------------------ Source/Scene/GroundPrimitive.js | 13 +- .../createShadowVolumeAppearanceShader.js | 15 +- Source/Shaders/ShadowVolumeVS.glsl | 24 ++- 6 files changed, 232 insertions(+), 208 deletions(-) create mode 100644 Source/Core/InversePlaneExtentsGeometryAttribute.js create mode 100644 Source/Core/MatrixColumnGeometryInstanceAttribute.js delete mode 100644 Source/Core/PlanarExtentsGeometryInstanceAttribute.js diff --git a/Source/Core/InversePlaneExtentsGeometryAttribute.js b/Source/Core/InversePlaneExtentsGeometryAttribute.js new file mode 100644 index 00000000000..8df2a14734d --- /dev/null +++ b/Source/Core/InversePlaneExtentsGeometryAttribute.js @@ -0,0 +1,108 @@ +define([ + './Cartesian3', + './Cartographic', + './ComponentDatatype', + './defineProperties', + './Matrix4' + ], function( + Cartesian3, + Cartographic, + ComponentDatatype, + defineProperties, + Matrix4) { + 'use strict'; + + var cartesianScratch1 = new Cartesian3(); + var cartesianScratch2 = new Cartesian3(); + function cartographicStraightDistances(cartographic1, cartographic2, ellipsoid) { + var a = Cartographic.toCartesian(cartographic1, ellipsoid, cartesianScratch1); + var b = Cartographic.toCartesian(cartographic2, ellipsoid, cartesianScratch2); + return Cartesian3.distance(a, b); + } + + var cartographic1Scratch = new Cartographic(); + var cartographic2Scratch = new Cartographic(); + function InversePlaneExtentsGeometryAttribute(rectangle, ellipsoid) { + // Compute greatest straight line distances from rectangle West -> East and rectangle South -> North. + // Sample at the corners and center of the rectangle + var carto1 = cartographic1Scratch; + var carto2 = cartographic2Scratch; + + carto1.longitude = rectangle.west; + carto2.longitude = rectangle.east; + carto1.latitude = rectangle.north; + carto2.latitude = rectangle.north; + var distanceWestEast = cartographicStraightDistances(carto1, carto2, ellipsoid); + + carto1.latitude = rectangle.south; + carto2.latitude = rectangle.south; + distanceWestEast = Math.max(cartographicStraightDistances(carto1, carto2, ellipsoid), distanceWestEast); + + carto1.latitude = (rectangle.south + rectangle.north) * 0.5; + carto2.latitude = carto1.latitude; + distanceWestEast = Math.max(cartographicStraightDistances(carto1, carto2, ellipsoid), distanceWestEast); + + carto1.latitude = rectangle.south; + carto2.latitude = rectangle.north; + carto1.longitude = rectangle.east; + carto2.longitude = rectangle.east; + var distanceSouthNorth = cartographicStraightDistances(carto1, carto2, ellipsoid); + + this.value = new Float32Array([1.0 / distanceWestEast, 1.0 / distanceSouthNorth]); + } + + defineProperties(InversePlaneExtentsGeometryAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link InversePlaneExtentsGeometryAttribute#value}. + * + * @memberof InversePlaneExtentsGeometryAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link InversePlaneExtentsGeometryAttribute#value}. + * + * @memberof InversePlaneExtentsGeometryAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 2 + */ + componentsPerAttribute : { + get : function() { + return 2; + } + }, + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @memberof InversePlaneExtentsGeometryAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + return InversePlaneExtentsGeometryAttribute; +}); diff --git a/Source/Core/MatrixColumnGeometryInstanceAttribute.js b/Source/Core/MatrixColumnGeometryInstanceAttribute.js new file mode 100644 index 00000000000..2e1e3e5680c --- /dev/null +++ b/Source/Core/MatrixColumnGeometryInstanceAttribute.js @@ -0,0 +1,87 @@ +define([ + './ComponentDatatype', + './defineProperties', + './Matrix4', + './Transforms' + ], function( + ComponentDatatype, + defineProperties, + Matrix4, + Transforms) { + 'use strict'; + + function MatrixColumnGeometryInstanceAttribute(array, startIndex) { + this.value = new Float32Array(array.slice(startIndex, startIndex + 4)); + } + + defineProperties(MatrixColumnGeometryInstanceAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link MatrixColumnGeometryInstanceAttribute#value}. + * + * @memberof MatrixColumnGeometryInstanceAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link MatrixColumnGeometryInstanceAttribute#value}. + * + * @memberof MatrixColumnGeometryInstanceAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 4 + */ + componentsPerAttribute : { + get : function() { + return 4; + } + }, + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @memberof MatrixColumnGeometryInstanceAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + + var transformScratch = new Matrix4(); + var packedArray = new Array(16).fill(0.0); + MatrixColumnGeometryInstanceAttribute.addAttributes = function(cartographicCenter, ellipsoid, attributes) { // TODO: this should prolly just swallow matrices + var transform = Transforms.eastNorthUpToFixedFrame(cartographicCenter, ellipsoid, transformScratch); + + Matrix4.pack(transform, packedArray); + // TODO: should probably accept a name + attributes.column0 = new MatrixColumnGeometryInstanceAttribute(packedArray, 0); + attributes.column1 = new MatrixColumnGeometryInstanceAttribute(packedArray, 4); + attributes.column2 = new MatrixColumnGeometryInstanceAttribute(packedArray, 8); + attributes.column3 = new MatrixColumnGeometryInstanceAttribute(packedArray, 12); + + console.log(packedArray); + } + + return MatrixColumnGeometryInstanceAttribute; +}); diff --git a/Source/Core/PlanarExtentsGeometryInstanceAttribute.js b/Source/Core/PlanarExtentsGeometryInstanceAttribute.js deleted file mode 100644 index e0b8f257de2..00000000000 --- a/Source/Core/PlanarExtentsGeometryInstanceAttribute.js +++ /dev/null @@ -1,193 +0,0 @@ -define([ - './Cartesian2', - './Cartesian3', - './Cartographic', - './Math', - './Check', - './ComponentDatatype', - './defineProperties', - './Ellipsoid', - './Plane' - ], function( - Cartesian2, - Cartesian3, - Cartographic, - CesiumMath, - Check, - ComponentDatatype, - defineProperties, - Ellipsoid, - Plane) { - 'use strict'; - - var forwardScratch = new Cartesian3(); - var normalScratch = new Cartesian3(); - var upScratch = new Cartesian3(); - var rightScratch = new Cartesian3(); - var planeScratch = new Plane(Cartesian3.UNIT_X, 0.0); - /** - * Plane extents needed when computing ground primitive texture coordinates per-instance. - * Used for "small distances." - * Consists of a normal of magnitude range and a distance. - * - * @alias PlanarExtentsGeometryInstanceAttribute - * @constructor - * - * @param {Cartesian3} rectangle Conservative bounding rectangle around the instance. - * @param {Cartesian3} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. - * - * @see GeometryInstance - * @see GeometryInstanceAttribute - * @see createShadowVolumeAppearanceShader - * @private - */ - function PlanarExtentsGeometryInstanceAttribute(origin, end) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('origin', origin); - Check.typeOf.object('end', end); - //>>includeEnd('debug'); - - var forward = Cartesian3.subtract(end, origin, forwardScratch); - forward = Cartesian3.normalize(forward, forward); - var up = Cartesian3.normalize(origin, upScratch); - var right = Cartesian3.cross(up, forward, rightScratch); - - var normal = Cartesian3.cross(up, right, normalScratch); - - var plane = planeScratch; - plane.normal = normal; - plane.distance = 0.0; - var planeDistance = Plane.getPointDistance(plane, end); - - this.value = new Float32Array([normal.x, normal.y, normal.z, 1.0 / planeDistance]); - } - - defineProperties(PlanarExtentsGeometryInstanceAttribute.prototype, { - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link PlanarExtentsGeometryInstanceAttribute#value}. - * - * @memberof PlanarExtentsGeometryInstanceAttribute.prototype - * - * @type {ComponentDatatype} - * @readonly - * - * @default {@link ComponentDatatype.FLOAT} - */ - componentDatatype : { - get : function() { - return ComponentDatatype.FLOAT; - } - }, - - /** - * The number of components in the attributes, i.e., {@link PlanarExtentsGeometryInstanceAttribute#value}. - * - * @memberof PlanarExtentsGeometryInstanceAttribute.prototype - * - * @type {Number} - * @readonly - * - * @default 4 - */ - componentsPerAttribute : { - get : function() { - return 4; - } - }, - - /** - * When true and componentDatatype is an integer format, - * indicate that the components should be mapped to the range [0, 1] (unsigned) - * or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * - * @memberof PlanarExtentsGeometryInstanceAttribute.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - normalize : { - get : function() { - return false; - } - } - }); - - var cartographicScratch = new Cartographic(); - var northMiddleScratch = new Cartesian3(); - var southMiddleScratch = new Cartesian3(); - /** - * Plane extents needed when computing ground primitive texture coordinates per-instance in the latitude direction. - * Used for "small distances." - * Consists of an oct-32 encoded normal packed to a float, a float distance, and a float range - * - * @alias PlanarExtentsGeometryInstanceAttribute - * @constructor - * - * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. - * @param {Ellipoid} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. - * - * @see GeometryInstance - * @see GeometryInstanceAttribute - * @see createShadowVolumeAppearanceShader - * @private - */ - PlanarExtentsGeometryInstanceAttribute.getLatitudeExtents = function(rectangle, ellipsoid) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - Check.typeOf.object('ellipsoid', ellipsoid); - //>>includeEnd('debug'); - - var cartographic = cartographicScratch; - - cartographic.latitude = rectangle.north; - cartographic.longitude = (rectangle.east + rectangle.west) * 0.5; - var northMiddle = Cartographic.toCartesian(cartographic, ellipsoid, northMiddleScratch); - - cartographic.latitude = rectangle.south; - var southMiddle = Cartographic.toCartesian(cartographic, ellipsoid, southMiddleScratch); - - return new PlanarExtentsGeometryInstanceAttribute(southMiddle, northMiddle); - } - - var eastMiddleScratch = new Cartesian3(); - var westMiddleScratch = new Cartesian3(); - - /** - * Plane extents needed when computing ground primitive texture coordinates per-instance in the longitude direction. - * Used for "small distances." - * Consists of an oct-32 encoded normal packed to a float, a float distance, and a float range - * - * @alias PlanarExtentsGeometryInstanceAttribute - * @constructor - * - * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. - * @param {Ellipoid} ellipsoid Ellipsoid for converting rectangle bounds to intertial fixed coordinates. - * - * @see GeometryInstance - * @see GeometryInstanceAttribute - * @see createShadowVolumeAppearanceShader - * @private - */ - PlanarExtentsGeometryInstanceAttribute.getLongitudeExtents = function(rectangle, ellipsoid) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - Check.typeOf.object('ellipsoid', ellipsoid); - //>>includeEnd('debug'); - - var cartographic = cartographicScratch; - - cartographic.latitude = (rectangle.north + rectangle.south) * 0.5; - cartographic.longitude = rectangle.east; - var eastMiddle = Cartographic.toCartesian(cartographic, ellipsoid, eastMiddleScratch); - - cartographic.longitude = rectangle.west; - var westMiddle = Cartographic.toCartesian(cartographic, ellipsoid, westMiddleScratch); - - return new PlanarExtentsGeometryInstanceAttribute(westMiddle, eastMiddle); - } - - return PlanarExtentsGeometryInstanceAttribute; -}); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 5728934179c..a6c8231f23f 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -15,7 +15,8 @@ define([ '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', - '../Core/PlanarExtentsGeometryInstanceAttribute', + '../Core/InversePlaneExtentsGeometryAttribute', + '../Core/MatrixColumnGeometryInstanceAttribute', '../Core/Rectangle', '../Core/Resource', '../Core/SphericalExtentsGeometryInstanceAttribute', @@ -42,7 +43,8 @@ define([ isArray, CesiumMath, OrientedBoundingBox, - PlanarExtentsGeometryInstanceAttribute, + InversePlaneExtentsGeometryAttribute, + MatrixColumnGeometryInstanceAttribute, Rectangle, Resource, SphericalExtentsGeometryInstanceAttribute, @@ -782,9 +784,12 @@ define([ var rectangle = getRectangle(frameState, geometry); var attributes = { sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(rectangle), - longitudePlaneExtents : PlanarExtentsGeometryInstanceAttribute.getLongitudeExtents(rectangle, ellipsoid), - latitudePlaneExtents : PlanarExtentsGeometryInstanceAttribute.getLatitudeExtents(rectangle, ellipsoid) + inversePlaneExtents : new InversePlaneExtentsGeometryAttribute(rectangle, ellipsoid) }; + + var rectangleCenter = Rectangle.center(rectangle, new Cartographic()); + MatrixColumnGeometryInstanceAttribute.addAttributes(Cartographic.toCartesian(rectangleCenter, ellipsoid, new Cartesian3()), ellipsoid, attributes); + // TODO: pick and choose? var instanceAttributes = instance.attributes; for (var attributeKey in instanceAttributes) { diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/createShadowVolumeAppearanceShader.js index daacdbe5e0c..1026bf42811 100644 --- a/Source/Scene/createShadowVolumeAppearanceShader.js +++ b/Source/Scene/createShadowVolumeAppearanceShader.js @@ -26,7 +26,7 @@ define([ Check.typeOf.object('appearance', appearance); //>>includeEnd('debug'); - var extentsCull = defaultValue(extentsCulling, true); + var extentsCull = defaultValue(extentsCulling, false); var smallExtents = defaultValue(small, true); if (appearance instanceof PerInstanceColorAppearance) { @@ -52,8 +52,9 @@ define([ if (extentsCull || usesSt) { glsl += 'varying vec4 v_sphericalExtents;\n' + - 'varying vec4 v_planarExtentsLatitude;\n' + - 'varying vec4 v_planarExtentsLongitude;\n'; + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n'; } glsl += getLocalFunctions(shaderDependencies, smallExtents); @@ -143,8 +144,8 @@ define([ if (smallExtents) { // TODO: add ability to do long-and-narrows? glsl += ' // Unpack planes and transform to eye space\n' + - ' float u = computePlanarTexcoord(v_planarExtentsLatitude, worldCoord);\n' + - ' float v = computePlanarTexcoord(v_planarExtentsLongitude, worldCoord);\n'; + ' float u = computePlanarTexcoord(v_southPlane, eyeCoord.xyz / eyeCoord.w, v_inversePlaneExtents.y);\n' + + ' float v = computePlanarTexcoord(v_westPlane, eyeCoord.xyz / eyeCoord.w, v_inversePlaneExtents.x);\n'; } else { glsl += ' // Treat world coords as a sphere normal for spherical coordinates\n' + @@ -209,9 +210,9 @@ define([ } if (shaderDependencies.requiresTexcoords && smallExtents) { glsl += - 'float computePlanarTexcoord(vec4 packedPlanarExtent, vec3 worldCoords) {\n' + + 'float computePlanarTexcoord(vec4 plane, vec3 eyeCoords, float inverseExtent) {\n' + ' // planar extent is a plane at the origin (so just a direction) and an extent distance.\n' + - ' return (dot(packedPlanarExtent.xyz, worldCoords)) * packedPlanarExtent.w;\n' + + ' return (dot(plane.xyz, eyeCoords) + plane.w) * inverseExtent;\n' + '}\n'; } return glsl; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 64f9297ac55..2273e8cc611 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -18,8 +18,9 @@ uniform float u_globeMinimumAltitude; #ifndef VECTOR_TILE varying vec4 v_sphericalExtents; -varying vec4 v_planarExtentsLatitude; -varying vec4 v_planarExtentsLongitude; +varying vec2 v_inversePlaneExtents; +varying vec4 v_westPlane; +varying vec4 v_southPlane; #endif #ifdef PER_INSTANCE_COLOR @@ -37,8 +38,23 @@ void main() #endif v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); - v_planarExtentsLatitude = czm_batchTable_latitudePlaneExtents(batchId); - v_planarExtentsLongitude = czm_batchTable_longitudePlaneExtents(batchId); + vec2 inversePlaneExtents = czm_batchTable_inversePlaneExtents(batchId); + + mat4 planesModel; + planesMatrix[0] = czm_batchTable_column0(batchId); + planesMatrix[1] = czm_batchTable_column1(batchId); + planesMatrix[2] = czm_batchTable_column2(batchId); + planesMatrix[3] = czm_batchTable_column3(batchId); + + // Planes in local ENU coordinate system + vec4 westPlane = vec4(1.0, 0.0, 0.0, -0.5 / inversePlaneExtents.x); + vec4 southPlane = vec4(0.0, 1.0, 0.0, -0.5 / inversePlaneExtents.y); + + mat4 planesModelView = czm_view * planesModel; + + v_inversePlaneExtents = inversePlaneExtents; + v_westPlane = czm_transformPlane(westPlane, planesModelView); + v_southPlane = czm_transformPlane(southPlane, planesModelView); vec4 position = czm_computePosition(); From 62ab0ba993ab8a0bd85c9f45fb141355039238da Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 29 Mar 2018 15:24:44 -0400 Subject: [PATCH 13/40] using double-precision points for planes, added switching between plane and spherical texcoords mode --- .../InversePlaneExtentsGeometryAttribute.js | 108 ------------- .../MatrixColumnGeometryInstanceAttribute.js | 87 ---------- ...ReferencePointGeometryInstanceAttribute.js | 150 ++++++++++++++++++ Source/Scene/ClassificationPrimitive.js | 52 ++++-- Source/Scene/GroundPrimitive.js | 49 ++++-- ...der.js => ShadowVolumeAppearanceShader.js} | 89 ++++++++--- Source/Shaders/ShadowVolumeVS.glsl | 36 +++-- 7 files changed, 314 insertions(+), 257 deletions(-) delete mode 100644 Source/Core/InversePlaneExtentsGeometryAttribute.js delete mode 100644 Source/Core/MatrixColumnGeometryInstanceAttribute.js create mode 100644 Source/Core/ReferencePointGeometryInstanceAttribute.js rename Source/Scene/{createShadowVolumeAppearanceShader.js => ShadowVolumeAppearanceShader.js} (83%) diff --git a/Source/Core/InversePlaneExtentsGeometryAttribute.js b/Source/Core/InversePlaneExtentsGeometryAttribute.js deleted file mode 100644 index 8df2a14734d..00000000000 --- a/Source/Core/InversePlaneExtentsGeometryAttribute.js +++ /dev/null @@ -1,108 +0,0 @@ -define([ - './Cartesian3', - './Cartographic', - './ComponentDatatype', - './defineProperties', - './Matrix4' - ], function( - Cartesian3, - Cartographic, - ComponentDatatype, - defineProperties, - Matrix4) { - 'use strict'; - - var cartesianScratch1 = new Cartesian3(); - var cartesianScratch2 = new Cartesian3(); - function cartographicStraightDistances(cartographic1, cartographic2, ellipsoid) { - var a = Cartographic.toCartesian(cartographic1, ellipsoid, cartesianScratch1); - var b = Cartographic.toCartesian(cartographic2, ellipsoid, cartesianScratch2); - return Cartesian3.distance(a, b); - } - - var cartographic1Scratch = new Cartographic(); - var cartographic2Scratch = new Cartographic(); - function InversePlaneExtentsGeometryAttribute(rectangle, ellipsoid) { - // Compute greatest straight line distances from rectangle West -> East and rectangle South -> North. - // Sample at the corners and center of the rectangle - var carto1 = cartographic1Scratch; - var carto2 = cartographic2Scratch; - - carto1.longitude = rectangle.west; - carto2.longitude = rectangle.east; - carto1.latitude = rectangle.north; - carto2.latitude = rectangle.north; - var distanceWestEast = cartographicStraightDistances(carto1, carto2, ellipsoid); - - carto1.latitude = rectangle.south; - carto2.latitude = rectangle.south; - distanceWestEast = Math.max(cartographicStraightDistances(carto1, carto2, ellipsoid), distanceWestEast); - - carto1.latitude = (rectangle.south + rectangle.north) * 0.5; - carto2.latitude = carto1.latitude; - distanceWestEast = Math.max(cartographicStraightDistances(carto1, carto2, ellipsoid), distanceWestEast); - - carto1.latitude = rectangle.south; - carto2.latitude = rectangle.north; - carto1.longitude = rectangle.east; - carto2.longitude = rectangle.east; - var distanceSouthNorth = cartographicStraightDistances(carto1, carto2, ellipsoid); - - this.value = new Float32Array([1.0 / distanceWestEast, 1.0 / distanceSouthNorth]); - } - - defineProperties(InversePlaneExtentsGeometryAttribute.prototype, { - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link InversePlaneExtentsGeometryAttribute#value}. - * - * @memberof InversePlaneExtentsGeometryAttribute.prototype - * - * @type {ComponentDatatype} - * @readonly - * - * @default {@link ComponentDatatype.FLOAT} - */ - componentDatatype : { - get : function() { - return ComponentDatatype.FLOAT; - } - }, - - /** - * The number of components in the attributes, i.e., {@link InversePlaneExtentsGeometryAttribute#value}. - * - * @memberof InversePlaneExtentsGeometryAttribute.prototype - * - * @type {Number} - * @readonly - * - * @default 2 - */ - componentsPerAttribute : { - get : function() { - return 2; - } - }, - - /** - * When true and componentDatatype is an integer format, - * indicate that the components should be mapped to the range [0, 1] (unsigned) - * or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * - * @memberof InversePlaneExtentsGeometryAttribute.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - normalize : { - get : function() { - return false; - } - } - }); - - return InversePlaneExtentsGeometryAttribute; -}); diff --git a/Source/Core/MatrixColumnGeometryInstanceAttribute.js b/Source/Core/MatrixColumnGeometryInstanceAttribute.js deleted file mode 100644 index 2e1e3e5680c..00000000000 --- a/Source/Core/MatrixColumnGeometryInstanceAttribute.js +++ /dev/null @@ -1,87 +0,0 @@ -define([ - './ComponentDatatype', - './defineProperties', - './Matrix4', - './Transforms' - ], function( - ComponentDatatype, - defineProperties, - Matrix4, - Transforms) { - 'use strict'; - - function MatrixColumnGeometryInstanceAttribute(array, startIndex) { - this.value = new Float32Array(array.slice(startIndex, startIndex + 4)); - } - - defineProperties(MatrixColumnGeometryInstanceAttribute.prototype, { - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link MatrixColumnGeometryInstanceAttribute#value}. - * - * @memberof MatrixColumnGeometryInstanceAttribute.prototype - * - * @type {ComponentDatatype} - * @readonly - * - * @default {@link ComponentDatatype.FLOAT} - */ - componentDatatype : { - get : function() { - return ComponentDatatype.FLOAT; - } - }, - - /** - * The number of components in the attributes, i.e., {@link MatrixColumnGeometryInstanceAttribute#value}. - * - * @memberof MatrixColumnGeometryInstanceAttribute.prototype - * - * @type {Number} - * @readonly - * - * @default 4 - */ - componentsPerAttribute : { - get : function() { - return 4; - } - }, - - /** - * When true and componentDatatype is an integer format, - * indicate that the components should be mapped to the range [0, 1] (unsigned) - * or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * - * @memberof MatrixColumnGeometryInstanceAttribute.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - normalize : { - get : function() { - return false; - } - } - }); - - - var transformScratch = new Matrix4(); - var packedArray = new Array(16).fill(0.0); - MatrixColumnGeometryInstanceAttribute.addAttributes = function(cartographicCenter, ellipsoid, attributes) { // TODO: this should prolly just swallow matrices - var transform = Transforms.eastNorthUpToFixedFrame(cartographicCenter, ellipsoid, transformScratch); - - Matrix4.pack(transform, packedArray); - // TODO: should probably accept a name - attributes.column0 = new MatrixColumnGeometryInstanceAttribute(packedArray, 0); - attributes.column1 = new MatrixColumnGeometryInstanceAttribute(packedArray, 4); - attributes.column2 = new MatrixColumnGeometryInstanceAttribute(packedArray, 8); - attributes.column3 = new MatrixColumnGeometryInstanceAttribute(packedArray, 12); - - console.log(packedArray); - } - - return MatrixColumnGeometryInstanceAttribute; -}); diff --git a/Source/Core/ReferencePointGeometryInstanceAttribute.js b/Source/Core/ReferencePointGeometryInstanceAttribute.js new file mode 100644 index 00000000000..844ef132029 --- /dev/null +++ b/Source/Core/ReferencePointGeometryInstanceAttribute.js @@ -0,0 +1,150 @@ +define([ + './Cartesian3', + './Cartographic', + './Check', + './ComponentDatatype', + './defined', + './defineProperties', + './EncodedCartesian3', + './Matrix4', + './Transforms' + ], function( + Cartesian3, + Cartographic, + Check, + ComponentDatatype, + defined, + defineProperties, + EncodedCartesian3, + Matrix4, + Transforms) { + 'use strict'; + + /** + * Batch table attribute representing the HIGH or LOW bits of an EncodedCartesian3. + * + * @param {Cartesian3} vec3 HIGH or LOW bits of an EncodedCartesian3 + * @private + */ + function ReferencePointGeometryInstanceAttribute(vec3) { + this.value = new Float32Array([vec3.x, vec3.y, vec3.z]); + } + + defineProperties(ReferencePointGeometryInstanceAttribute.prototype, { + /** + * The datatype of each component in the attribute, e.g., individual elements in + * {@link ReferencePointGeometryInstanceAttribute#value}. + * + * @memberof ReferencePointGeometryInstanceAttribute.prototype + * + * @type {ComponentDatatype} + * @readonly + * + * @default {@link ComponentDatatype.FLOAT} + */ + componentDatatype : { + get : function() { + return ComponentDatatype.FLOAT; + } + }, + + /** + * The number of components in the attributes, i.e., {@link ReferencePointGeometryInstanceAttribute#value}. + * + * @memberof ReferencePointGeometryInstanceAttribute.prototype + * + * @type {Number} + * @readonly + * + * @default 3 + */ + componentsPerAttribute : { + get : function() { + return 3; + } + }, + + /** + * When true and componentDatatype is an integer format, + * indicate that the components should be mapped to the range [0, 1] (unsigned) + * or [-1, 1] (signed) when they are accessed as floating-point for rendering. + * + * @memberof ReferencePointGeometryInstanceAttribute.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + normalize : { + get : function() { + return false; + } + } + }); + + var encodeScratch = new EncodedCartesian3(); + function addAttributesForPoint(point, name, attributes) { + var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); + attributes[name + '_HIGH'] = new ReferencePointGeometryInstanceAttribute(encoded.high); + attributes[name + '_LOW'] = new ReferencePointGeometryInstanceAttribute(encoded.low); + } + + var cartographicScratch = new Cartographic(); + var cornerScratch = new Cartesian3(); + var northWestScratch = new Cartesian3(); + var southEastScratch = new Cartesian3(); + /** + * Gets a set of 6 GeometryInstanceAttributes containing double-precision points in world/CBF space. + * These points can be used to form planes, which can then be used to compute per-fragment texture coordinates + * over a small rectangle area. + * + * @param {Rectangle} rectangle Rectangle bounds over which texture coordinates should be computed. + * @param {Ellipsoid} ellipsoid Ellipsoid for computing CBF/World coordinates from the rectangle. + * @private + */ + ReferencePointGeometryInstanceAttribute.getAttributesForPlanes = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + //>>includeEnd('debug'); + + // Compute corner positions in double precision + var carto = cartographicScratch; + carto.longitude = rectangle.west; + carto.latitude = rectangle.south; + + var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); + + carto.latitude = rectangle.north; + var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); + + carto.longitude = rectangle.east; + carto.latitude = rectangle.south; + var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); + + var attributes = {}; + addAttributesForPoint(corner, 'southWest', attributes); + addAttributesForPoint(northWest, 'northWest', attributes); + addAttributesForPoint(southEast, 'southEast', attributes); + return attributes; + }; + + /** + * Checks if the given attributes contain all the attributes needed for double-precision planes. + * + * @param {Object} attributes Attributes object. + * @return {Boolean} Whether the attributes contain all the attributes for double-precision planes. + * @private + */ + ReferencePointGeometryInstanceAttribute.hasAttributesForPlanes = function(attributes) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('attributes', attributes); + //>>includeEnd('debug'); + return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && + defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && + defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW); + }; + + return ReferencePointGeometryInstanceAttribute; +}); diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index fc9835cd939..d9df35177c6 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -8,6 +8,7 @@ define([ '../Core/DeveloperError', '../Core/GeometryInstance', '../Core/Rectangle', + '../Core/ReferencePointGeometryInstanceAttribute', '../Core/WebGLConstants', '../Core/isArray', '../Renderer/DrawCommand', @@ -20,13 +21,13 @@ define([ '../ThirdParty/when', './BlendingState', './ClassificationType', - './createShadowVolumeAppearanceShader', './DepthFunction', './Material', './MaterialAppearance', './PerInstanceColorAppearance', './Primitive', './SceneMode', + './ShadowVolumeAppearanceShader', './StencilFunction', './StencilOperation' ], function( @@ -39,6 +40,7 @@ define([ DeveloperError, GeometryInstance, Rectangle, + ReferencePointGeometryInstanceAttribute, WebGLConstants, isArray, DrawCommand, @@ -51,13 +53,13 @@ define([ when, BlendingState, ClassificationType, - createShadowVolumeAppearanceShader, DepthFunction, Material, MaterialAppearance, PerInstanceColorAppearance, Primitive, SceneMode, + ShadowVolumeAppearanceShader, StencilFunction, StencilOperation) { 'use strict'; @@ -198,7 +200,9 @@ define([ var hasPerColorAttribute = false; var hasSphericalExtentsAttribute = false; - var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; + var hasPlanarExtentsAttributes = false; + + var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; var geometryInstanceCount = geometryInstancesArray.length; for (var i = 0; i < geometryInstanceCount; i++) { var attributes = geometryInstancesArray[i].attributes; @@ -212,7 +216,13 @@ define([ hasSphericalExtentsAttribute = true; } else if (hasSphericalExtentsAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } + } + if (ReferencePointGeometryInstanceAttribute.hasAttributesForPlanes(attributes)) { + hasPlanarExtentsAttributes = true; + } else if (hasPlanarExtentsAttributes) { + throw new DeveloperError('All GeometryInstances must have the same attributes.'); + } + } else if (hasPerColorAttribute || hasSphericalExtentsAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); } @@ -228,13 +238,14 @@ define([ throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); } - // TODO: SphericalExtents needed if PerInstanceColor isn't all the same - if (defined(appearance.material) && !hasSphericalExtentsAttribute) { - throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute'); + // TODO: SphericalExtents or PlanarExtents needed if PerInstanceColor isn't all the same + if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { + throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute or GeometryInstanceAttributes for computing planes'); } this._hasPerColorAttribute = hasPerColorAttribute; this._hasSphericalExtentsAttribute = hasSphericalExtentsAttribute; + this._hasPlanarExtentsAttributes = hasPlanarExtentsAttributes; this.appearance = appearance; var readOnlyAttributes; @@ -642,21 +653,32 @@ define([ var appearance = classificationPrimitive.appearance; var isPerInstanceColor = appearance instanceof PerInstanceColorAppearance; - var vsColorSource = new ShaderSource({ - defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : [], - sources : [vs] - }); - var parts; // Create a fragment shader that computes only required material hookups using screen space techniques - var shadowVolumeAppearanceFS = createShadowVolumeAppearanceShader(appearance); + var usePlanarExtents = classificationPrimitive._hasPlanarExtentsAttributes; + var cullUsingExtents = classificationPrimitive._hasPlanarExtentsAttributes || classificationPrimitive._hasSphericalExtentsAttribute; + var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(appearance, cullUsingExtents, usePlanarExtents); + var shadowVolumeAppearanceFS = shadowVolumeAppearanceShader.fragmentShaderSource; if (isPerInstanceColor) { parts = [shadowVolumeAppearanceFS]; } else { parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS]; } + var colorVSDefines = isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : []; + if (shadowVolumeAppearanceShader.usesTexcoords) { + if (shadowVolumeAppearanceShader.planarExtents) { + colorVSDefines.push('PLANAR_EXTENTS'); + } else { + colorVSDefines.push('SPHERICAL_EXTENTS'); + } + } + var vsColorSource = new ShaderSource({ + defines : colorVSDefines, + sources : [vs] + }); + var fsColorSource = new ShaderSource({ sources : [parts.join('\n')] }); @@ -1024,8 +1046,8 @@ define([ // Update primitive appearance if (this._primitive.appearance !== appearance) { // Check if the appearance is supported by the geometry attributes - if (!this._hasSphericalExtentsAttribute && defined(appearance.material)) { - throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute'); + if (!this._hasSphericalExtentsAttribute && !this._hasPlanarExtentsAttributes && defined(appearance.material)) { + throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute or GeometryInstanceAttributes for computing planes'); } if (!this._hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index a6c8231f23f..1279fbeb29e 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -15,9 +15,8 @@ define([ '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', - '../Core/InversePlaneExtentsGeometryAttribute', - '../Core/MatrixColumnGeometryInstanceAttribute', '../Core/Rectangle', + '../Core/ReferencePointGeometryInstanceAttribute', '../Core/Resource', '../Core/SphericalExtentsGeometryInstanceAttribute', '../Renderer/Pass', @@ -43,9 +42,8 @@ define([ isArray, CesiumMath, OrientedBoundingBox, - InversePlaneExtentsGeometryAttribute, - MatrixColumnGeometryInstanceAttribute, Rectangle, + ReferencePointGeometryInstanceAttribute, Resource, SphericalExtentsGeometryInstanceAttribute, Pass, @@ -776,21 +774,34 @@ define([ this._minHeight = this._minTerrainHeight * exaggeration; this._maxHeight = this._maxTerrainHeight * exaggeration; + // Determine whether to add spherical or planar extent attributes + var usePlanarExtents = true; + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometry = instance.geometry; + rectangle = getRectangle(frameState, geometry); + if (shouldUseSpherical(rectangle)) { + usePlanarExtents = false; + break; + } + } + for (i = 0; i < length; ++i) { instance = instances[i]; geometry = instance.geometry; instanceType = geometry.constructor; - var rectangle = getRectangle(frameState, geometry); - var attributes = { - sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(rectangle), - inversePlaneExtents : new InversePlaneExtentsGeometryAttribute(rectangle, ellipsoid) - }; + rectangle = getRectangle(frameState, geometry); + var attributes; - var rectangleCenter = Rectangle.center(rectangle, new Cartographic()); - MatrixColumnGeometryInstanceAttribute.addAttributes(Cartographic.toCartesian(rectangleCenter, ellipsoid, new Cartesian3()), ellipsoid, attributes); + if (usePlanarExtents) { + attributes = ReferencePointGeometryInstanceAttribute.getAttributesForPlanes(rectangle, ellipsoid, attributes); + } else { + attributes = { + sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(rectangle) + }; + } - // TODO: pick and choose? var instanceAttributes = instance.attributes; for (var attributeKey in instanceAttributes) { if (instanceAttributes.hasOwnProperty(attributeKey)) { @@ -839,6 +850,10 @@ define([ this._classificationPrimitive.update(frameState); }; + function shouldUseSpherical(rectangle) { + return Math.max(rectangle.width, rectangle.height) > GroundPrimitive.MAX_WIDTH_FOR_PLANAR_EXTENTS; + } + /** * @private */ @@ -909,5 +924,15 @@ define([ return destroyObject(this); }; + /** + * 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 + */ + GroundPrimitive.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0); + return GroundPrimitive; }); diff --git a/Source/Scene/createShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js similarity index 83% rename from Source/Scene/createShadowVolumeAppearanceShader.js rename to Source/Scene/ShadowVolumeAppearanceShader.js index 1026bf42811..db8ffb9115f 100644 --- a/Source/Scene/createShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -21,19 +21,61 @@ define([ * @returns {String} Shader source for a fragment shader using the input appearance. * @private */ - function createShadowVolumeAppearanceShader(appearance, extentsCulling, small) { + function ShadowVolumeAppearanceShader(appearance, extentsCulling, planarExtents) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('appearance', appearance); //>>includeEnd('debug'); - var extentsCull = defaultValue(extentsCulling, false); - var smallExtents = defaultValue(small, true); + this._extentsCulling = defaultValue(extentsCulling, false); + this._planarExtents = defaultValue(planarExtents, false); + this._shaderSource = createShadowVolumeAppearanceShader(appearance, this._extentsCulling, this._planarExtents); + this._usesTexcoords = shaderDependenciesScratch._requiresTexcoords; + } - if (appearance instanceof PerInstanceColorAppearance) { - return getPerInstanceColorShader(extentsCull, appearance.flat, smallExtents); + defineProperties(ShadowVolumeAppearanceShader.prototype, { + /** + * Whether or not the resulting shader uses texture coordinates. + * + * @memberof ShadowVolumeAppearanceShader.prototype + * @type {Boolean} + * @readonly + */ + usesTexcoords : { + get : function() { + return this._usesTexcoords; + } + }, + /** + * Whether or not the resulting shader's texture coordinates are computed from planar extents. + * + * @memberof ShadowVolumeAppearanceShader.prototype + * @type {Boolean} + * @readonly + */ + planarExtents : { + get : function() { + return this._planarExtents; + } + }, + /** + * The fragment shader source. + * @memberof ShadowVolumeAppearanceShader.prototype + * @type {String} + * @readonly + */ + fragmentShaderSource : { + get : function() { + return this._shaderSource; + } } + }); + function createShadowVolumeAppearanceShader(appearance, extentsCull, planarExtents) { var shaderDependencies = shaderDependenciesScratch.reset(); + if (appearance instanceof PerInstanceColorAppearance) { + return getPerInstanceColorShader(extentsCull, appearance.flat, planarExtents); + } + shaderDependencies.requiresTexcoords = extentsCull; shaderDependencies.requiresEyeCoord = !appearance.flat; @@ -50,20 +92,21 @@ define([ '#extension GL_EXT_frag_depth : enable\n' + '#endif\n'; if (extentsCull || usesSt) { - glsl += - 'varying vec4 v_sphericalExtents;\n' + + glsl += planarExtents ? 'varying vec2 v_inversePlaneExtents;\n' + 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n'; + 'varying vec4 v_southPlane;\n' : + + 'varying vec4 v_sphericalExtents;\n'; } - glsl += getLocalFunctions(shaderDependencies, smallExtents); + glsl += getLocalFunctions(shaderDependencies, planarExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCull, smallExtents); + glsl += getDependenciesAndCulling(shaderDependencies, extentsCull, planarExtents); glsl += ' czm_materialInput materialInput;\n'; if (usesNormalEC) { @@ -90,26 +133,30 @@ define([ return glsl; } - function getPerInstanceColorShader(extentsCulling, flatShading, smallExtents) { + function getPerInstanceColorShader(extentsCulling, flatShading, planarExtents) { var glsl = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + '#endif\n' + 'varying vec4 v_color;\n'; if (extentsCulling) { - glsl += + glsl += planarExtents ? + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' : + 'varying vec4 v_sphericalExtents;\n'; } - var shaderDependencies = shaderDependenciesScratch.reset(); + var shaderDependencies = shaderDependenciesScratch; shaderDependencies.requiresTexcoords = extentsCulling; shaderDependencies.requiresNormalEC = !flatShading; - glsl += getLocalFunctions(shaderDependencies, smallExtents); + glsl += getLocalFunctions(shaderDependencies, planarExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, smallExtents); + glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents); if (flatShading) { glsl += @@ -129,7 +176,7 @@ define([ return glsl; } - function getDependenciesAndCulling(shaderDependencies, extentsCulling, smallExtents) { + function getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents) { var glsl = ''; if (shaderDependencies.requiresEyeCoord) { glsl += @@ -141,7 +188,7 @@ define([ ' vec3 worldCoord = worldCoord4.xyz / worldCoord4.w;\n'; } if (shaderDependencies.requiresTexcoords) { - if (smallExtents) { // TODO: add ability to do long-and-narrows? + if (planarExtents) { // TODO: add ability to do long-and-narrows? glsl += ' // Unpack planes and transform to eye space\n' + ' float u = computePlanarTexcoord(v_southPlane, eyeCoord.xyz / eyeCoord.w, v_inversePlaneExtents.y);\n' + @@ -172,7 +219,7 @@ define([ return glsl; } - function getLocalFunctions(shaderDependencies, smallExtents) { + function getLocalFunctions(shaderDependencies, planarExtents) { var glsl = ''; if (shaderDependencies.requiresEyeCoord) { glsl += @@ -196,7 +243,6 @@ define([ ' // Sample depths at both offset and negative offset\n' + ' float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 + positiveOffset) / czm_viewport.zw));\n' + ' float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 - positiveOffset) / czm_viewport.zw));\n' + - // TODO: could re-ordering here help performance? do texture fetches, then logic, then unpack? ' // Explicitly evaluate both paths\n' + ' bvec2 upOrRightInBounds = lessThan(fragCoord2 + positiveOffset, czm_viewport.zw);\n' + ' float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + @@ -208,10 +254,9 @@ define([ ' return (upOrRightEC - (eyeCoord.xyz / eyeCoord.w)) * useUpOrRight + ((eyeCoord.xyz / eyeCoord.w) - downOrLeftEC) * useDownOrLeft;\n' + '}\n'; } - if (shaderDependencies.requiresTexcoords && smallExtents) { + if (shaderDependencies.requiresTexcoords && planarExtents) { glsl += 'float computePlanarTexcoord(vec4 plane, vec3 eyeCoords, float inverseExtent) {\n' + - ' // planar extent is a plane at the origin (so just a direction) and an extent distance.\n' + ' return (dot(plane.xyz, eyeCoords) + plane.w) * inverseExtent;\n' + '}\n'; } @@ -298,5 +343,5 @@ define([ } }); - return createShadowVolumeAppearanceShader; + return ShadowVolumeAppearanceShader; }); diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 2273e8cc611..55a0c9e1262 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -17,12 +17,19 @@ uniform float u_globeMinimumAltitude; #endif #ifndef VECTOR_TILE + +#ifdef SPHERICAL_EXTENTS varying vec4 v_sphericalExtents; +#endif + +#ifdef PLANAR_EXTENTS varying vec2 v_inversePlaneExtents; varying vec4 v_westPlane; varying vec4 v_southPlane; #endif +#endif + #ifdef PER_INSTANCE_COLOR varying vec4 v_color; #endif @@ -37,24 +44,27 @@ void main() v_color = czm_batchTable_color(batchId); #endif +#ifdef SPHERICAL_EXTENTS v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); - vec2 inversePlaneExtents = czm_batchTable_inversePlaneExtents(batchId); +#endif - mat4 planesModel; - planesMatrix[0] = czm_batchTable_column0(batchId); - planesMatrix[1] = czm_batchTable_column1(batchId); - planesMatrix[2] = czm_batchTable_column2(batchId); - planesMatrix[3] = czm_batchTable_column3(batchId); +#ifdef PLANAR_EXTENTS + vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz; + vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz; + vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz; - // Planes in local ENU coordinate system - vec4 westPlane = vec4(1.0, 0.0, 0.0, -0.5 / inversePlaneExtents.x); - vec4 southPlane = vec4(0.0, 1.0, 0.0, -0.5 / inversePlaneExtents.y); + vec3 eastWard = southEastCorner - southWestCorner; + float eastExtent = length(eastWard); + eastWard /= eastExtent; - mat4 planesModelView = czm_view * planesModel; + vec3 northWard = northWestCorner - southWestCorner; + float northExtent = length(northWard); + northWard /= northExtent; - v_inversePlaneExtents = inversePlaneExtents; - v_westPlane = czm_transformPlane(westPlane, planesModelView); - v_southPlane = czm_transformPlane(southPlane, planesModelView); + v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); + v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); + v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); +#endif vec4 position = czm_computePosition(); From 2cb51327ad55cbd64b931ee2fe4eaa6c8215ff8e Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 29 Mar 2018 18:52:55 -0400 Subject: [PATCH 14/40] add batching for ground primitives --- .../gallery/batchingGroundPrims.html | 22 +- LICENSE.md | 32 + Source/Core/RectangleRbush.js | 70 +++ Source/DataSources/GeometryVisualizer.js | 4 +- .../StaticGroundGeometryPerMaterialBatch.js | 36 +- Source/Scene/GroundPrimitive.js | 17 + Source/ThirdParty/quickselect.js | 59 ++ Source/ThirdParty/rbush.js | 561 ++++++++++++++++++ 8 files changed, 775 insertions(+), 26 deletions(-) create mode 100644 Source/Core/RectangleRbush.js create mode 100644 Source/ThirdParty/quickselect.js create mode 100644 Source/ThirdParty/rbush.js diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html index dccf66dbad4..f4d551db807 100644 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ b/Apps/Sandcastle/gallery/batchingGroundPrims.html @@ -78,7 +78,7 @@ new Cesium.Cartesian3(-2352875.095159641, -3742564.819171856, 4582173.540953957), new Cesium.Cartesian3(-2350669.646050987, -3743751.6823160048, 4582334.8406995395) ]; - +/* // concave polygon var redPolygon1 = viewer.entities.add({ name : 'concave polygon on surface', @@ -87,7 +87,7 @@ material : '../images/Cesium_Logo_Color.jpg' } }); - +*/ // polygons with non-overlapping extents seem to be batchable without problems /* var redPolygon1 = viewer.entities.add({ @@ -136,7 +136,7 @@ });*/ // nearly overlapping rectangles over mt. st. helens -/* + var latitude = 46.1922; var longitude = -122.1934; @@ -167,7 +167,12 @@ material : Cesium.Color.YELLOW.withAlpha(0.5) } }); -*/ + +var checkerboard = new Cesium.CheckerboardMaterialProperty({ + evenColor : Cesium.Color.ORANGE, + oddColor : Cesium.Color.YELLOW, + repeat : new Cesium.Cartesian2(14, 14) +}); var rightHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas); rightHandler.setInputAction(function(movement) { @@ -181,17 +186,12 @@ var normalized = Cesium.Cartesian3.normalize(cartesian, cartesian); console.log(normalized); - viewer.entities.removeAll(); + //viewer.entities.removeAll(); viewer.entities.add({ name : lat + ' ' + long, rectangle : { coordinates : Cesium.Rectangle.fromDegrees(long - 0.0002, lat - 0.0001, long + 0.0002, lat + 0.0001), - material : - new Cesium.CheckerboardMaterialProperty({ - evenColor : Cesium.Color.ORANGE, - oddColor : Cesium.Color.YELLOW, - repeat : new Cesium.Cartesian2(14, 14) - }) + material : checkerboard } }); } diff --git a/LICENSE.md b/LICENSE.md index 4d8563a6fcd..c718fa62f7b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -509,6 +509,38 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +### rbush + +https://github.com/mourner/rbush + +> MIT License + +> Copyright (c) 2016 Vladimir Agafonkin + +>Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +> +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +> +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +### quickselect + +https://github.com/mourner/quickselect + +No license given, used by rbush + ### crunch https://github.com/BinomialLLC/crunch diff --git a/Source/Core/RectangleRbush.js b/Source/Core/RectangleRbush.js new file mode 100644 index 00000000000..3525d8eca9e --- /dev/null +++ b/Source/Core/RectangleRbush.js @@ -0,0 +1,70 @@ +define([ + '../ThirdParty/rbush', + './Check' + ], function( + rbush, + Check) { + 'use strict'; + + /** + * Wrapper around rbush for use with Rectangle types. + * @private + */ + function RectangleRbush() { + this._tree = rbush(); + } + + function RectangleWithId() { + this.minX = 0.0; + this.minY = 0.0; + this.maxX = 0.0; + this.maxY = 0.0; + this.id = ''; + } + + function fromRectangleAndId(rectangle, id, result) { + result.minX = rectangle.west; + result.minY = rectangle.south; + result.maxX = rectangle.east; + result.maxY = rectangle.north; + result.id = id; + return result; + } + + function idCompare(a, b) { + return a.id === b.id; + } + + RectangleRbush.prototype.insert = function(id, rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('id', id); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = fromRectangleAndId(rectangle, id, new RectangleWithId()); + this._tree.insert(withId); + }; + + var removalScratch = new RectangleWithId(); + RectangleRbush.prototype.remove = function(id, rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('id', id); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = fromRectangleAndId(rectangle, id, removalScratch); + this._tree.remove(withId, idCompare); + }; + + var collisionScratch = new RectangleWithId(); + RectangleRbush.prototype.collides = function(rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = fromRectangleAndId(rectangle, '', collisionScratch); + return this._tree.collides(withId); + }; + + return RectangleRbush; +}); diff --git a/Source/DataSources/GeometryVisualizer.js b/Source/DataSources/GeometryVisualizer.js index 209533b3011..7e98ab5dd01 100644 --- a/Source/DataSources/GeometryVisualizer.js +++ b/Source/DataSources/GeometryVisualizer.js @@ -26,7 +26,6 @@ define([ './RectangleGeometryUpdater', './StaticGeometryColorBatch', './StaticGeometryPerMaterialBatch', - './StaticGroundGeometryColorBatch', './StaticGroundGeometryPerMaterialBatch', './StaticOutlineGeometryBatch', './WallGeometryUpdater' @@ -58,7 +57,6 @@ define([ RectangleGeometryUpdater, StaticGeometryColorBatch, StaticGeometryPerMaterialBatch, - StaticGroundGeometryColorBatch, StaticGroundGeometryPerMaterialBatch, StaticOutlineGeometryBatch, WallGeometryUpdater) { @@ -163,7 +161,7 @@ define([ this._groundMaterialBatches = new Array(numberOfClassificationTypes); // TODO: why is this? for (i = 0; i < numberOfClassificationTypes; ++i) { - this._groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, PerInstanceColorAppearance, i); + this._groundColorBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance, i); this._groundMaterialBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance, i); } diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index 85c2c61b2ca..9d4c9255354 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -6,6 +6,7 @@ define([ '../Core/DistanceDisplayCondition', '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/ShowGeometryInstanceAttribute', + '../Core/RectangleRbush', '../Scene/GroundPrimitive', './BoundingSphereState', './ColorMaterialProperty', @@ -19,6 +20,7 @@ define([ DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, ShowGeometryInstanceAttribute, + RectangleRbush, GroundPrimitive, BoundingSphereState, ColorMaterialProperty, @@ -29,13 +31,13 @@ define([ var distanceDisplayConditionScratch = new DistanceDisplayCondition(); // Encapsulates a Primitive and all the entities that it represents. - function Batch(primitives, appearanceType, materialProperty, shadows) { + function Batch(primitives, appearanceType, materialProperty, shadows, usingSphericalCoordinates) { this.primitives = primitives; // scene level primitive collection this.appearanceType = appearanceType; this.materialProperty = materialProperty; this.updaters = new AssociativeArray(); this.createPrimitive = true; - this.primitive = undefined; + this.primitive = undefined; // a GroundPrimitive encapsulating all the entities this.oldPrimitive = undefined; this.geometry = new AssociativeArray(); this.material = undefined; @@ -46,30 +48,34 @@ define([ this.subscriptions = new AssociativeArray(); this.showsUpdated = new AssociativeArray(); this.shadows = shadows; + this.usingSphericalCoordinates = usingSphericalCoordinates; + this.rbush = new RectangleRbush(); } Batch.prototype.onMaterialChanged = function() { this.invalidated = true; }; - Batch.prototype.nonOverlapping = function(updater) { - return false; + Batch.prototype.nonOverlapping = function(rectangle) { + return !this.rbush.collides(rectangle); }; Batch.prototype.isMaterial = function(updater) { var material = this.materialProperty; var updaterMaterial = updater.fillMaterialProperty; - if (updaterMaterial === material) { + if (updaterMaterial === material || + (updaterMaterial instanceof ColorMaterialProperty && material instanceof ColorMaterialProperty)) { return true; } return defined(material) && material.equals(updaterMaterial); }; - Batch.prototype.add = function(time, updater) { + Batch.prototype.add = function(time, updater, geometryInstance) { var id = updater.id; this.updaters.set(id, updater); - this.geometry.set(id, updater.createFillGeometryInstance(time)); + this.geometry.set(id, geometryInstance); + this.rbush.insert(id, geometryInstance.geometry.rectangle); if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { this.updatersWithAttributes.set(id, updater); } else { @@ -85,8 +91,10 @@ define([ Batch.prototype.remove = function(updater) { var id = updater.id; + var geometryInstance = this.geometry.get(id); this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; if (this.updaters.remove(id)) { + this.rbush.remove(id, geometryInstance.geometry.rectangle); this.updatersWithAttributes.remove(id); var unsubscribe = this.subscriptions.get(id); if (defined(unsubscribe)) { @@ -309,15 +317,19 @@ define([ StaticGroundGeometryPerMaterialBatch.prototype.add = function(time, updater) { var items = this._items; var length = items.length; - for (var i = 0; i < length; i++) { + var geometryInstance = updater.createFillGeometryInstance(time); + var usingSphericalCoordinates = GroundPrimitive.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); + for (var i = 0; i < length; ++i) { var item = items[i]; - if (item.isMaterial(updater) && item.nonOverlapping(updater)) { - item.add(time, updater); + if (item.isMaterial(updater) && + item.nonOverlapping(geometryInstance.geometry.rectangle) && + item.usingSphericalCoordinates === usingSphericalCoordinates) { + item.add(time, updater, geometryInstance); return; } } - var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._shadows); - batch.add(time, updater); + var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._shadows, usingSphericalCoordinates); + batch.add(time, updater, geometryInstance); items.push(batch); }; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 1279fbeb29e..86fa9fcd16c 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -5,6 +5,7 @@ define([ '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', + '../Core/Check', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -32,6 +33,7 @@ define([ Cartesian3, Cartesian4, Cartographic, + Check, defaultValue, defined, defineProperties, @@ -924,6 +926,21 @@ define([ return destroyObject(this); }; + /** + * 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 + */ + GroundPrimitive.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. diff --git a/Source/ThirdParty/quickselect.js b/Source/ThirdParty/quickselect.js new file mode 100644 index 00000000000..a474563cc4f --- /dev/null +++ b/Source/ThirdParty/quickselect.js @@ -0,0 +1,59 @@ +define([], function() { +'use strict'; + +function quickselect(arr, k, left, right, compare) { + quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare); +}; + +function quickselectStep(arr, k, left, right, compare) { + + while (right > left) { + if (right - left > 600) { + var n = right - left + 1; + var m = k - left + 1; + var z = Math.log(n); + var s = 0.5 * Math.exp(2 * z / 3); + var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); + var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); + var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); + quickselectStep(arr, k, newLeft, newRight, compare); + } + + var t = arr[k]; + var i = left; + var j = right; + + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); + + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } + + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + +function defaultCompare(a, b) { + return a < b ? -1 : a > b ? 1 : 0; +} + +return quickselect; +}); diff --git a/Source/ThirdParty/rbush.js b/Source/ThirdParty/rbush.js new file mode 100644 index 00000000000..bbb7b6fbc61 --- /dev/null +++ b/Source/ThirdParty/rbush.js @@ -0,0 +1,561 @@ +define(['./quickselect'], function(quickselect) { +'use strict'; + +function rbush(maxEntries, format) { + if (!(this instanceof rbush)) return new rbush(maxEntries, format); + + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries || 9); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + + if (format) { + this._initFormat(format); + } + + this.clear(); +} + +rbush.prototype = { + + all: function () { + return this._all(this.data, []); + }, + + search: function (bbox) { + + var node = this.data, + result = [], + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return result; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return result; + }, + + collides: function (bbox) { + + var node = this.data, + toBBox = this.toBBox; + + if (!intersects(bbox, node)) return false; + + var nodesToSearch = [], + i, len, child, childBBox; + + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child; + + if (intersects(bbox, childBBox)) { + if (node.leaf || contains(bbox, childBBox)) return true; + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + + return false; + }, + + load: function (data) { + if (!(data && data.length)) return this; + + if (data.length < this._minEntries) { + for (var i = 0, len = data.length; i < len; i++) { + this.insert(data[i]); + } + return this; + } + + // recursively build the tree with the given data from scratch using OMT algorithm + var node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; + + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + var tmpNode = this.data; + this.data = node; + node = tmpNode; + } + + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } + + return this; + }, + + insert: function (item) { + if (item) this._insert(item, this.data.height - 1); + return this; + }, + + clear: function () { + this.data = createNode([]); + return this; + }, + + remove: function (item, equalsFn) { + if (!item) return this; + + var node = this.data, + bbox = this.toBBox(item), + path = [], + indexes = [], + i, parent, index, goingUp; + + // depth-first iterative tree traversal + while (node || path.length) { + + if (!node) { // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } + + if (node.leaf) { // check current node + index = findItem(item, node.children, equalsFn); + + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; + } + } + + if (!goingUp && !node.leaf && contains(node, bbox)) { // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + + } else if (parent) { // go right + i++; + node = parent.children[i]; + goingUp = false; + + } else node = null; // nothing found + } + + return this; + }, + + toBBox: function (item) { return item; }, + + compareMinX: compareNodeMinX, + compareMinY: compareNodeMinY, + + toJSON: function () { return this.data; }, + + fromJSON: function (data) { + this.data = data; + return this; + }, + + _all: function (node, result) { + var nodesToSearch = []; + while (node) { + if (node.leaf) result.push.apply(result, node.children); + else nodesToSearch.push.apply(nodesToSearch, node.children); + + node = nodesToSearch.pop(); + } + return result; + }, + + _build: function (items, left, right, height) { + + var N = right - left + 1, + M = this._maxEntries, + node; + + if (N <= M) { + // reached leaf level; return leaf + node = createNode(items.slice(left, right + 1)); + calcBBox(node, this.toBBox); + return node; + } + + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); + + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } + + node = createNode([]); + node.leaf = false; + node.height = height; + + // split the items into M mostly square tiles + + var N2 = Math.ceil(N / M), + N1 = N2 * Math.ceil(Math.sqrt(M)), + i, j, right2, right3; + + multiSelect(items, left, right, N1, this.compareMinX); + + for (i = left; i <= right; i += N1) { + + right2 = Math.min(i + N1 - 1, right); + + multiSelect(items, i, right2, N2, this.compareMinY); + + for (j = i; j <= right2; j += N2) { + + right3 = Math.min(j + N2 - 1, right2); + + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } + } + + calcBBox(node, this.toBBox); + + return node; + }, + + _chooseSubtree: function (bbox, node, level, path) { + + var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; + + while (true) { + path.push(node); + + if (node.leaf || path.length - 1 === level) break; + + minArea = minEnlargement = Infinity; + + for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; + area = bboxArea(child); + enlargement = enlargedArea(bbox, child) - area; + + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } + } + } + + node = targetNode || node.children[0]; + } + + return node; + }, + + _insert: function (item, level, isNode) { + + var toBBox = this.toBBox, + bbox = isNode ? item : toBBox(item), + insertPath = []; + + // find the best node for accommodating the item, saving all nodes along the path too + var node = this._chooseSubtree(bbox, this.data, level, insertPath); + + // put the item into the node + node.children.push(item); + extend(node, bbox); + + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } + + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + }, + + // split overflowed node into two + _split: function (insertPath, level) { + + var node = insertPath[level], + M = node.children.length, + m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + var splitIndex = this._chooseSplitIndex(node, m, M); + + var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); + newNode.height = node.height; + newNode.leaf = node.leaf; + + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); + + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + }, + + _splitRoot: function (node, newNode) { + // split root node + this.data = createNode([node, newNode]); + this.data.height = node.height + 1; + this.data.leaf = false; + calcBBox(this.data, this.toBBox); + }, + + _chooseSplitIndex: function (node, m, M) { + + var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; + + minOverlap = minArea = Infinity; + + for (i = m; i <= M - m; i++) { + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); + + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); + + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + + minArea = area < minArea ? area : minArea; + + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; + } + } + } + + return index; + }, + + // sorts node children by the best axis for split + _chooseSplitAxis: function (node, m, M) { + + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, + xMargin = this._allDistMargin(node, m, M, compareMinX), + yMargin = this._allDistMargin(node, m, M, compareMinY); + + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + }, + + // total margin of all possible split distributions where each node is at least m full + _allDistMargin: function (node, m, M, compare) { + + node.children.sort(compare); + + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), + i, child; + + for (i = m; i < M - m; i++) { + child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(leftBBox); + } + + for (i = M - m - 1; i >= m; i--) { + child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child); + margin += bboxMargin(rightBBox); + } + + return margin; + }, + + _adjustParentBBoxes: function (bbox, path, level) { + // adjust bboxes along the given tree path + for (var i = level; i >= 0; i--) { + extend(path[i], bbox); + } + }, + + _condense: function (path) { + // go through the path, removing empty nodes and updating bboxes + for (var i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + + } else this.clear(); + + } else calcBBox(path[i], this.toBBox); + } + }, + + _initFormat: function (format) { + // data format (minX, minY, maxX, maxY accessors) + + // uses eval-type function compilation instead of just accepting a toBBox function + // because the algorithms are very sensitive to sorting functions performance, + // so they should be dead simple and without inner calls + + var compareArr = ['return a', ' - b', ';']; + + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + + this.toBBox = new Function('a', + 'return {minX: a' + format[0] + + ', minY: a' + format[1] + + ', maxX: a' + format[2] + + ', maxY: a' + format[3] + '};'); + } +}; + +function findItem(item, items, equalsFn) { + if (!equalsFn) return items.indexOf(item); + + for (var i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; +} + +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + distBBox(node, 0, node.children.length, toBBox, node); +} + +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox, destNode) { + if (!destNode) destNode = createNode(null); + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(destNode, node.leaf ? toBBox(child) : child); + } + + return destNode; +} + +function extend(a, b) { + a.minX = Math.min(a.minX, b.minX); + a.minY = Math.min(a.minY, b.minY); + a.maxX = Math.max(a.maxX, b.maxX); + a.maxY = Math.max(a.maxY, b.maxY); + return a; +} + +function compareNodeMinX(a, b) { return a.minX - b.minX; } +function compareNodeMinY(a, b) { return a.minY - b.minY; } + +function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); } +function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); } + +function enlargedArea(a, b) { + return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * + (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY)); +} + +function intersectionArea(a, b) { + var minX = Math.max(a.minX, b.minX), + minY = Math.max(a.minY, b.minY), + maxX = Math.min(a.maxX, b.maxX), + maxY = Math.min(a.maxY, b.maxY); + + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} + +function contains(a, b) { + return a.minX <= b.minX && + a.minY <= b.minY && + b.maxX <= a.maxX && + b.maxY <= a.maxY; +} + +function intersects(a, b) { + return b.minX <= a.maxX && + b.minY <= a.maxY && + b.maxX >= a.minX && + b.maxY >= a.minY; +} + +function createNode(children) { + return { + children: children, + height: 1, + leaf: true, + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity + }; +} + +// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +// combines selection algorithm with binary divide & conquer approach + +function multiSelect(arr, left, right, n, compare) { + var stack = [left, right], + mid; + + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + + if (right - left <= n) continue; + + mid = left + Math.ceil((right - left) / n / 2) * n; + quickselect(arr, mid, left, right, compare); + + stack.push(left, mid, mid, right); + } +} + +return rbush; +}); From 92127960477f5b705cdc5fa12851447218c04885 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 5 Apr 2018 15:59:35 -0400 Subject: [PATCH 15/40] PR feedback --- Apps/Sandcastle/gallery/Hello World.html | 70 ------ .../gallery/batchingGroundPrims.html | 220 ------------------ Apps/Sandcastle/gallery/testDepth.html | 136 ----------- LICENSE.md | 18 +- Source/Core/Math.js | 13 +- Source/Core/RectangleCollisionChecker.js | 90 +++++++ Source/Core/RectangleRbush.js | 70 ------ ...ReferencePointGeometryInstanceAttribute.js | 150 ------------ ...hericalExtentsGeometryInstanceAttribute.js | 143 ------------ Source/DataSources/CorridorGeometryUpdater.js | 4 +- Source/DataSources/EllipseGeometryUpdater.js | 4 +- Source/DataSources/PolygonGeometryUpdater.js | 3 +- .../DataSources/RectangleGeometryUpdater.js | 4 +- .../StaticGroundGeometryPerMaterialBatch.js | 12 +- Source/Scene/ClassificationPrimitive.js | 169 ++++++++++++-- Source/Scene/GroundPrimitive.js | 14 +- Source/Scene/ShadowVolumeAppearanceShader.js | 132 +++++------ .../approximateSphericalCoordinates.glsl | 19 +- .../Functions/branchFreeTernaryFloat.glsl | 17 ++ Specs/Renderer/BuiltinFunctionsSpec.js | 10 + 20 files changed, 386 insertions(+), 912 deletions(-) delete mode 100644 Apps/Sandcastle/gallery/batchingGroundPrims.html delete mode 100644 Apps/Sandcastle/gallery/testDepth.html create mode 100644 Source/Core/RectangleCollisionChecker.js delete mode 100644 Source/Core/RectangleRbush.js delete mode 100644 Source/Core/ReferencePointGeometryInstanceAttribute.js delete mode 100644 Source/Core/SphericalExtentsGeometryInstanceAttribute.js create mode 100644 Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl diff --git a/Apps/Sandcastle/gallery/Hello World.html b/Apps/Sandcastle/gallery/Hello World.html index e22601b8792..640e8e31770 100644 --- a/Apps/Sandcastle/gallery/Hello World.html +++ b/Apps/Sandcastle/gallery/Hello World.html @@ -28,76 +28,6 @@ 'use strict'; //Sandcastle_Begin var viewer = new Cesium.Viewer('cesiumContainer'); - -var instance = new Cesium.GeometryInstance({ - geometry : new Cesium.EllipseGeometry({ - center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0), - semiMinorAxis : 500000.0, - semiMajorAxis : 1000000.0, - rotation : Cesium.Math.PI_OVER_FOUR, - vertexFormat : Cesium.VertexFormat.POSITION_AND_ST - }), - id : 'object returned when this instance is picked and to get/set per-instance attributes' -}); - -var checkerboardMaterial = Cesium.Material.fromType('Checkerboard'); - -var ellipsoidSurfaceAppearance1 = new Cesium.EllipsoidSurfaceAppearance({ - material : checkerboardMaterial -}); - -var stripeMaterial = Cesium.Material.fromType('Stripe'); - -var ellipsoidSurfaceAppearance2 = new Cesium.EllipsoidSurfaceAppearance({ - material : stripeMaterial -}); - -var perInstanceColorAppearance = new Cesium.PerInstanceColorAppearance({ - flat : true -}); - -var elPrimitive = viewer.scene.primitives.add(new Cesium.GroundPrimitive({ - geometryInstances : instance, - appearance : ellipsoidSurfaceAppearance1 -})); -/* -var greenRectangle = viewer.entities.add({ - name : 'Green translucent, rotated, and extruded rectangle at height with outline', - rectangle : { - coordinates : Cesium.Rectangle.fromRadians(-1.8582078985933261, 0.23654029197299448, -1.6230525464414889, 0.4570541830085829), - material : Cesium.Color.GREEN.withAlpha(0.5), - height : 0, - outline : true, // height must be set for outline to display - outlineColor : Cesium.Color.BLACK - } -}); -*/ -Sandcastle.addToolbarButton('switch appearances to stripe material', function() { - ellipsoidSurfaceAppearance1.material = stripeMaterial; - ellipsoidSurfaceAppearance2.material = stripeMaterial; -}); - -Sandcastle.addToolbarButton('switch appearances to check material', function() { - ellipsoidSurfaceAppearance1.material = checkerboardMaterial; - ellipsoidSurfaceAppearance2.material = checkerboardMaterial; -}); - -Sandcastle.addToolbarButton('switch to appearance 1', function() { - elPrimitive.appearance = ellipsoidSurfaceAppearance1; -}); - -Sandcastle.addToolbarButton('switch to appearance 2', function() { - elPrimitive.appearance = ellipsoidSurfaceAppearance2; -}); - -Sandcastle.addToolbarButton('switch to color appearance', function() { - elPrimitive.appearance = perInstanceColorAppearance; -}); - -Sandcastle.addToolbarButton('switch to undefined appearance', function() { - elPrimitive.appearance = undefined; -}); - //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Apps/Sandcastle/gallery/batchingGroundPrims.html b/Apps/Sandcastle/gallery/batchingGroundPrims.html deleted file mode 100644 index f4d551db807..00000000000 --- a/Apps/Sandcastle/gallery/batchingGroundPrims.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -

-

Loading...

-
- - - diff --git a/Apps/Sandcastle/gallery/testDepth.html b/Apps/Sandcastle/gallery/testDepth.html deleted file mode 100644 index 313bad0b4cf..00000000000 --- a/Apps/Sandcastle/gallery/testDepth.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -
-

Loading...

-
- - - diff --git a/LICENSE.md b/LICENSE.md index c718fa62f7b..abd8b643d3d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -539,7 +539,21 @@ THE SOFTWARE. https://github.com/mourner/quickselect -No license given, used by rbush +> ISC License + +> Copyright (c) 2018, Vladimir Agafonkin + +>Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. +> +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. ### crunch @@ -702,7 +716,7 @@ https://github.com/KhronosGroup/glTF-WebGL-PBR >CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE >OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -### ShaderFastLibs (adapted code) +### ShaderFastLibs https://github.com/michaldrobot/ShaderFastLibs diff --git a/Source/Core/Math.js b/Source/Core/Math.js index e26db0547a5..cb38e0c5356 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -1,10 +1,12 @@ define([ '../ThirdParty/mersenne-twister', + './Check', './defaultValue', './defined', './DeveloperError' ], function( MersenneTwister, + Check, defaultValue, defined, DeveloperError) { @@ -867,9 +869,12 @@ define([ * * @param {Number} x An input number in the range [-1, 1] * @returns {Number} An approximation of atan(x) - * @private */ CesiumMath.fastApproximateAtan = function(x) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number('x', x); + //>>includeEnd('debug'); + return x * (-0.1784 * Math.abs(x) - 0.0663 * x * x + 1.0301); }; @@ -881,9 +886,13 @@ define([ * @param {Number} x An input number that isn't zero if y is zero. * @param {Number} y An input number that isn't zero if x is zero. * @returns {Number} An approximation of atan2(x, y) - * @private */ CesiumMath.fastApproximateAtan2 = function(x, y) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.number('x', x); + Check.typeOf.number('y', y); + //>>includeEnd('debug'); + // atan approximations are usually only reliable over [-1, 1] // So reduce the range by flipping whether x or y is on top. var opposite, adjacent, t; // t used as swap and atan result. diff --git a/Source/Core/RectangleCollisionChecker.js b/Source/Core/RectangleCollisionChecker.js new file mode 100644 index 00000000000..400942c27b1 --- /dev/null +++ b/Source/Core/RectangleCollisionChecker.js @@ -0,0 +1,90 @@ +define([ + '../ThirdParty/rbush', + './Check' + ], function( + rbush, + Check) { + 'use strict'; + + /** + * Wrapper around rbush for use with Rectangle types. + * @private + */ + function RectangleCollisionChecker() { + this._tree = rbush(); + } + + function RectangleWithId() { + this.minX = 0.0; + this.minY = 0.0; + this.maxX = 0.0; + this.maxY = 0.0; + this.id = ''; + } + + RectangleWithId.fromRectangleAndId = function(id, rectangle, result) { + result.minX = rectangle.west; + result.minY = rectangle.south; + result.maxX = rectangle.east; + result.maxY = rectangle.north; + result.id = id; + return result; + }; + + /** + * Insert a rectangle into the collision checker. + * + * @param {String} id Unique string ID for the rectangle being inserted. + * @param {Rectangle} rectangle A Rectangle + * @private + */ + RectangleCollisionChecker.prototype.insert = function(id, rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('id', id); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = RectangleWithId.fromRectangleAndId(id, rectangle, new RectangleWithId()); + this._tree.insert(withId); + }; + + function idCompare(a, b) { + return a.id === b.id; + } + + var removalScratch = new RectangleWithId(); + /** + * Remove a rectangle from the collision checker. + * + * @param {String} id Unique string ID for the rectangle being removed. + * @param {Rectangle} rectangle A Rectangle + * @private + */ + RectangleCollisionChecker.prototype.remove = function(id, rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('id', id); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = RectangleWithId.fromRectangleAndId(id, rectangle, removalScratch); + this._tree.remove(withId, idCompare); + }; + + var collisionScratch = new RectangleWithId(); + /** + * Checks if a given rectangle collides with any of the rectangles in the collection. + * + * @param {Rectangle} rectangle A Rectangle that should be checked against the rectangles in the collision checker. + * @returns {Boolean} Whether the rectangle collides with any of the rectangles in the collision checker. + */ + RectangleCollisionChecker.prototype.collides = function(rectangle) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + //>>includeEnd('debug'); + + var withId = RectangleWithId.fromRectangleAndId('', rectangle, collisionScratch); + return this._tree.collides(withId); + }; + + return RectangleCollisionChecker; +}); diff --git a/Source/Core/RectangleRbush.js b/Source/Core/RectangleRbush.js deleted file mode 100644 index 3525d8eca9e..00000000000 --- a/Source/Core/RectangleRbush.js +++ /dev/null @@ -1,70 +0,0 @@ -define([ - '../ThirdParty/rbush', - './Check' - ], function( - rbush, - Check) { - 'use strict'; - - /** - * Wrapper around rbush for use with Rectangle types. - * @private - */ - function RectangleRbush() { - this._tree = rbush(); - } - - function RectangleWithId() { - this.minX = 0.0; - this.minY = 0.0; - this.maxX = 0.0; - this.maxY = 0.0; - this.id = ''; - } - - function fromRectangleAndId(rectangle, id, result) { - result.minX = rectangle.west; - result.minY = rectangle.south; - result.maxX = rectangle.east; - result.maxY = rectangle.north; - result.id = id; - return result; - } - - function idCompare(a, b) { - return a.id === b.id; - } - - RectangleRbush.prototype.insert = function(id, rectangle) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.string('id', id); - Check.typeOf.object('rectangle', rectangle); - //>>includeEnd('debug'); - - var withId = fromRectangleAndId(rectangle, id, new RectangleWithId()); - this._tree.insert(withId); - }; - - var removalScratch = new RectangleWithId(); - RectangleRbush.prototype.remove = function(id, rectangle) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.string('id', id); - Check.typeOf.object('rectangle', rectangle); - //>>includeEnd('debug'); - - var withId = fromRectangleAndId(rectangle, id, removalScratch); - this._tree.remove(withId, idCompare); - }; - - var collisionScratch = new RectangleWithId(); - RectangleRbush.prototype.collides = function(rectangle) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - //>>includeEnd('debug'); - - var withId = fromRectangleAndId(rectangle, '', collisionScratch); - return this._tree.collides(withId); - }; - - return RectangleRbush; -}); diff --git a/Source/Core/ReferencePointGeometryInstanceAttribute.js b/Source/Core/ReferencePointGeometryInstanceAttribute.js deleted file mode 100644 index 844ef132029..00000000000 --- a/Source/Core/ReferencePointGeometryInstanceAttribute.js +++ /dev/null @@ -1,150 +0,0 @@ -define([ - './Cartesian3', - './Cartographic', - './Check', - './ComponentDatatype', - './defined', - './defineProperties', - './EncodedCartesian3', - './Matrix4', - './Transforms' - ], function( - Cartesian3, - Cartographic, - Check, - ComponentDatatype, - defined, - defineProperties, - EncodedCartesian3, - Matrix4, - Transforms) { - 'use strict'; - - /** - * Batch table attribute representing the HIGH or LOW bits of an EncodedCartesian3. - * - * @param {Cartesian3} vec3 HIGH or LOW bits of an EncodedCartesian3 - * @private - */ - function ReferencePointGeometryInstanceAttribute(vec3) { - this.value = new Float32Array([vec3.x, vec3.y, vec3.z]); - } - - defineProperties(ReferencePointGeometryInstanceAttribute.prototype, { - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link ReferencePointGeometryInstanceAttribute#value}. - * - * @memberof ReferencePointGeometryInstanceAttribute.prototype - * - * @type {ComponentDatatype} - * @readonly - * - * @default {@link ComponentDatatype.FLOAT} - */ - componentDatatype : { - get : function() { - return ComponentDatatype.FLOAT; - } - }, - - /** - * The number of components in the attributes, i.e., {@link ReferencePointGeometryInstanceAttribute#value}. - * - * @memberof ReferencePointGeometryInstanceAttribute.prototype - * - * @type {Number} - * @readonly - * - * @default 3 - */ - componentsPerAttribute : { - get : function() { - return 3; - } - }, - - /** - * When true and componentDatatype is an integer format, - * indicate that the components should be mapped to the range [0, 1] (unsigned) - * or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * - * @memberof ReferencePointGeometryInstanceAttribute.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - normalize : { - get : function() { - return false; - } - } - }); - - var encodeScratch = new EncodedCartesian3(); - function addAttributesForPoint(point, name, attributes) { - var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); - attributes[name + '_HIGH'] = new ReferencePointGeometryInstanceAttribute(encoded.high); - attributes[name + '_LOW'] = new ReferencePointGeometryInstanceAttribute(encoded.low); - } - - var cartographicScratch = new Cartographic(); - var cornerScratch = new Cartesian3(); - var northWestScratch = new Cartesian3(); - var southEastScratch = new Cartesian3(); - /** - * Gets a set of 6 GeometryInstanceAttributes containing double-precision points in world/CBF space. - * These points can be used to form planes, which can then be used to compute per-fragment texture coordinates - * over a small rectangle area. - * - * @param {Rectangle} rectangle Rectangle bounds over which texture coordinates should be computed. - * @param {Ellipsoid} ellipsoid Ellipsoid for computing CBF/World coordinates from the rectangle. - * @private - */ - ReferencePointGeometryInstanceAttribute.getAttributesForPlanes = function(rectangle, ellipsoid) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - Check.typeOf.object('ellipsoid', ellipsoid); - //>>includeEnd('debug'); - - // Compute corner positions in double precision - var carto = cartographicScratch; - carto.longitude = rectangle.west; - carto.latitude = rectangle.south; - - var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); - - carto.latitude = rectangle.north; - var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); - - carto.longitude = rectangle.east; - carto.latitude = rectangle.south; - var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); - - var attributes = {}; - addAttributesForPoint(corner, 'southWest', attributes); - addAttributesForPoint(northWest, 'northWest', attributes); - addAttributesForPoint(southEast, 'southEast', attributes); - return attributes; - }; - - /** - * Checks if the given attributes contain all the attributes needed for double-precision planes. - * - * @param {Object} attributes Attributes object. - * @return {Boolean} Whether the attributes contain all the attributes for double-precision planes. - * @private - */ - ReferencePointGeometryInstanceAttribute.hasAttributesForPlanes = function(attributes) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('attributes', attributes); - //>>includeEnd('debug'); - return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && - defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && - defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW); - }; - - return ReferencePointGeometryInstanceAttribute; -}); diff --git a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js b/Source/Core/SphericalExtentsGeometryInstanceAttribute.js deleted file mode 100644 index 796c1691d0a..00000000000 --- a/Source/Core/SphericalExtentsGeometryInstanceAttribute.js +++ /dev/null @@ -1,143 +0,0 @@ -define([ - './Cartesian2', - './Cartesian3', - './Cartographic', - './Check', - './Math', - './ComponentDatatype', - './defineProperties', - './Ellipsoid' - ], function( - Cartesian2, - Cartesian3, - Cartographic, - Check, - CesiumMath, - ComponentDatatype, - defineProperties, - Ellipsoid) { - 'use strict'; - - function approximateSphericalLatitude(spherePoint) { - // Project into plane with vertical for latitude - var magXY = Math.sqrt(spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y); - return CesiumMath.fastApproximateAtan2(magXY, spherePoint.z); - } - - function approximateSphericalLongitude(spherePoint) { - return CesiumMath.fastApproximateAtan2(spherePoint.x, spherePoint.y); - } - - var cartographicScratch = new Cartographic(); - var cartesian3Scratch = new Cartesian3(); - function latLongToSpherical(latitude, longitude, result) { - var carto = cartographicScratch; - carto.latitude = latitude; - carto.longitude = longitude; - carto.height = 0.0; - - var cartesian = Cartographic.toCartesian(carto, Ellipsoid.WGS84, cartesian3Scratch); - var sphereLatitude = approximateSphericalLatitude(cartesian); - var sphereLongitude = approximateSphericalLongitude(cartesian); - result.x = sphereLatitude; - result.y = sphereLongitude; - - return result; - } - - var sphericalScratch = new Cartesian2(); - - /** - * Spherical extents needed when computing ground primitive texture coordinates per-instance. - * Used for "large distances." - * Computation is matched to in-shader approximations. - * - * Consists of western and southern spherical coordinates and inverse ranges. - * - * @alias SphericalExtentsGeometryInstanceAttribute - * @constructor - * - * @param {Rectangle} rectangle Conservative bounding rectangle around the instance. - * - * @see GeometryInstance - * @see GeometryInstanceAttribute - * @see createShadowVolumeAppearanceShader - * @private - */ - function SphericalExtentsGeometryInstanceAttribute(rectangle) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - //>>includeEnd('debug'); - - // rectangle cartographic coords !== spherical because it's on an ellipsoid - var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, sphericalScratch); - - // Slightly pad extents to avoid floating point error when fragment culling at edges. - var south = southWestExtents.x - CesiumMath.EPSILON5; - var west = southWestExtents.y - CesiumMath.EPSILON5; - - var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, sphericalScratch); - var north = northEastExtents.x + CesiumMath.EPSILON5; - var east = northEastExtents.y + CesiumMath.EPSILON5; - - var longitudeRangeInverse = 1.0 / (east - west); - var latitudeRangeInverse = 1.0 / (north - south); - - this.value = new Float32Array([west, south, longitudeRangeInverse, latitudeRangeInverse]); - } - - defineProperties(SphericalExtentsGeometryInstanceAttribute.prototype, { - /** - * The datatype of each component in the attribute, e.g., individual elements in - * {@link SphericalExtentsGeometryInstanceAttribute#value}. - * - * @memberof SphericalExtentsGeometryInstanceAttribute.prototype - * - * @type {ComponentDatatype} - * @readonly - * - * @default {@link ComponentDatatype.FLOAT} - */ - componentDatatype : { - get : function() { - return ComponentDatatype.FLOAT; - } - }, - - /** - * The number of components in the attributes, i.e., {@link SphericalExtentsGeometryInstanceAttribute#value}. - * - * @memberof SphericalExtentsGeometryInstanceAttribute.prototype - * - * @type {Number} - * @readonly - * - * @default 4 - */ - componentsPerAttribute : { - get : function() { - return 4; - } - }, - - /** - * When true and componentDatatype is an integer format, - * indicate that the components should be mapped to the range [0, 1] (unsigned) - * or [-1, 1] (signed) when they are accessed as floating-point for rendering. - * - * @memberof SphericalExtentsGeometryInstanceAttribute.prototype - * - * @type {Boolean} - * @readonly - * - * @default false - */ - normalize : { - get : function() { - return false; - } - } - }); - - return SphericalExtentsGeometryInstanceAttribute; -}); diff --git a/Source/DataSources/CorridorGeometryUpdater.js b/Source/DataSources/CorridorGeometryUpdater.js index dcf281359a6..1351b25b692 100644 --- a/Source/DataSources/CorridorGeometryUpdater.js +++ b/Source/DataSources/CorridorGeometryUpdater.js @@ -165,10 +165,8 @@ define([ }; CorridorGeometryUpdater.prototype._isOnTerrain = function(entity, corridor) { - //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; - return this._fillEnabled && !defined(corridor.height) && !defined(corridor.extrudedHeight) && - GroundPrimitive.isSupported(this._scene);// && isColorMaterial; + GroundPrimitive.isSupported(this._scene); }; CorridorGeometryUpdater.prototype._getIsClosed = function(options) { diff --git a/Source/DataSources/EllipseGeometryUpdater.js b/Source/DataSources/EllipseGeometryUpdater.js index ec3f535408d..1da875c75e6 100644 --- a/Source/DataSources/EllipseGeometryUpdater.js +++ b/Source/DataSources/EllipseGeometryUpdater.js @@ -172,9 +172,7 @@ define([ }; EllipseGeometryUpdater.prototype._isOnTerrain = function(entity, ellipse) { - //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; - - return this._fillEnabled && !defined(ellipse.height) && !defined(ellipse.extrudedHeight) && GroundPrimitive.isSupported(this._scene); //&& isColorMaterial; + return this._fillEnabled && !defined(ellipse.height) && !defined(ellipse.extrudedHeight) && GroundPrimitive.isSupported(this._scene); }; EllipseGeometryUpdater.prototype._isDynamic = function(entity, ellipse) { diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index 0f3067d3ece..cf537ebd95f 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -173,10 +173,9 @@ define([ }; PolygonGeometryUpdater.prototype._isOnTerrain = function(entity, polygon) { - //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; var perPositionHeightProperty = polygon.perPositionHeight; var perPositionHeightEnabled = defined(perPositionHeightProperty) && (perPositionHeightProperty.isConstant ? perPositionHeightProperty.getValue(Iso8601.MINIMUM_VALUE) : true); - return this._fillEnabled && !defined(polygon.height) && !defined(polygon.extrudedHeight) &&// isColorMaterial && + return this._fillEnabled && !defined(polygon.height) && !defined(polygon.extrudedHeight) && !perPositionHeightEnabled && GroundPrimitive.isSupported(this._scene); }; diff --git a/Source/DataSources/RectangleGeometryUpdater.js b/Source/DataSources/RectangleGeometryUpdater.js index a336bf4827c..256b17a1cb6 100644 --- a/Source/DataSources/RectangleGeometryUpdater.js +++ b/Source/DataSources/RectangleGeometryUpdater.js @@ -167,9 +167,7 @@ define([ }; RectangleGeometryUpdater.prototype._isOnTerrain = function(entity, rectangle) { - //var isColorMaterial = this._materialProperty instanceof ColorMaterialProperty; - - return this._fillEnabled && !defined(rectangle.height) && !defined(rectangle.extrudedHeight) && GroundPrimitive.isSupported(this._scene); // && isColorMaterial; + return this._fillEnabled && !defined(rectangle.height) && !defined(rectangle.extrudedHeight) && GroundPrimitive.isSupported(this._scene); }; RectangleGeometryUpdater.prototype._isDynamic = function(entity, rectangle) { diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index 9d4c9255354..d0c1971a121 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -6,7 +6,7 @@ define([ '../Core/DistanceDisplayCondition', '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/ShowGeometryInstanceAttribute', - '../Core/RectangleRbush', + '../Core/RectangleCollisionChecker', '../Scene/GroundPrimitive', './BoundingSphereState', './ColorMaterialProperty', @@ -20,7 +20,7 @@ define([ DistanceDisplayCondition, DistanceDisplayConditionGeometryInstanceAttribute, ShowGeometryInstanceAttribute, - RectangleRbush, + RectangleCollisionChecker, GroundPrimitive, BoundingSphereState, ColorMaterialProperty, @@ -49,7 +49,7 @@ define([ this.showsUpdated = new AssociativeArray(); this.shadows = shadows; this.usingSphericalCoordinates = usingSphericalCoordinates; - this.rbush = new RectangleRbush(); + this.rectangleCollisionCheck = new RectangleCollisionChecker(); } Batch.prototype.onMaterialChanged = function() { @@ -57,7 +57,7 @@ define([ }; Batch.prototype.nonOverlapping = function(rectangle) { - return !this.rbush.collides(rectangle); + return !this.rectangleCollisionCheck.collides(rectangle); }; Batch.prototype.isMaterial = function(updater) { @@ -75,7 +75,7 @@ define([ var id = updater.id; this.updaters.set(id, updater); this.geometry.set(id, geometryInstance); - this.rbush.insert(id, geometryInstance.geometry.rectangle); + this.rectangleCollisionCheck.insert(id, geometryInstance.geometry.rectangle); if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { this.updatersWithAttributes.set(id, updater); } else { @@ -94,7 +94,7 @@ define([ var geometryInstance = this.geometry.get(id); this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; if (this.updaters.remove(id)) { - this.rbush.remove(id, geometryInstance.geometry.rectangle); + this.rectangleCollisionCheck.remove(id, geometryInstance.geometry.rectangle); this.updatersWithAttributes.remove(id); var unsubscribe = this.subscriptions.get(id); if (defined(unsubscribe)) { diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index d9df35177c6..9ac42e6a9fb 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -1,14 +1,21 @@ define([ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Math', + '../Core/Check', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', + '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/EncodedCartesian3', '../Core/GeometryInstance', + '../Core/GeometryInstanceAttribute', '../Core/Rectangle', - '../Core/ReferencePointGeometryInstanceAttribute', '../Core/WebGLConstants', '../Core/isArray', '../Renderer/DrawCommand', @@ -31,16 +38,23 @@ define([ './StencilFunction', './StencilOperation' ], function( + Cartesian2, + Cartesian3, + Cartographic, + CesiumMath, + Check, ColorGeometryInstanceAttribute, combine, + ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, DeveloperError, + EncodedCartesian3, GeometryInstance, + GeometryInstanceAttribute, Rectangle, - ReferencePointGeometryInstanceAttribute, WebGLConstants, isArray, DrawCommand, @@ -67,10 +81,9 @@ define([ var ClassificationPrimitiveReadOnlyInstanceAttributes = ['color']; /** - * A classification primitive represents a volume enclosing geometry in the {@link Scene} to be highlighted. The geometry must be from a single {@link GeometryInstance}. - * Batching multiple geometries is not yet supported. + * A classification primitive represents a volume enclosing geometry in the {@link Scene} to be highlighted. *

- * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including + * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. @@ -217,26 +230,25 @@ define([ } else if (hasSphericalExtentsAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); } - if (ReferencePointGeometryInstanceAttribute.hasAttributesForPlanes(attributes)) { + if (hasAttributesForTextureCoordinatePlanes(attributes)) { hasPlanarExtentsAttributes = true; } else if (hasPlanarExtentsAttributes) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); } - } else if (hasPerColorAttribute || hasSphericalExtentsAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } + } } // If attributes include color and appearance is undefined, default to a color appearance if (!defined(appearance) && hasPerColorAttribute) { - appearance = new PerInstanceColorAppearance({ - flat : true - }); - } + appearance = new PerInstanceColorAppearance({ + flat : true + }); + } if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); - } + } // TODO: SphericalExtents or PlanarExtents needed if PerInstanceColor isn't all the same if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { @@ -667,7 +679,7 @@ define([ } var colorVSDefines = isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : []; - if (shadowVolumeAppearanceShader.usesTexcoords) { + if (shadowVolumeAppearanceShader.usesTextureCoordinates) { if (shadowVolumeAppearanceShader.planarExtents) { colorVSDefines.push('PLANAR_EXTENTS'); } else { @@ -1120,5 +1132,134 @@ define([ return destroyObject(this); }; + function hasAttributesForTextureCoordinatePlanes(attributes) { + return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && + defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && + defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW); + } + + var encodeScratch = new EncodedCartesian3(); + function addAttributesForPoint(point, name, attributes) { + var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); + + attributes[name + '_HIGH'] = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(encoded.high, [0, 0, 0]) + }); + + attributes[name + '_LOW'] = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(encoded.low, [0, 0, 0]) + }); + } + + var cartographicScratch = new Cartographic(); + var cornerScratch = new Cartesian3(); + var northWestScratch = new Cartesian3(); + var southEastScratch = new Cartesian3(); + /** + * Gets an attributes object containing 3 high-precision points as 6 GeometryInstanceAttributes. + * These points are used to compute eye-space planes, which are then used to compute texture + * coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances. + * @see ShadowVolumeAppearanceShader + * @private + * + * @param {Rectangle} rectangle Rectangle object that the points will approximately bound + * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + */ + ClassificationPrimitive.getAttributesForTextureCoordinatePlanes = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + //>>includeEnd('debug'); + + // Compute corner positions in double precision + var carto = cartographicScratch; + carto.longitude = rectangle.west; + carto.latitude = rectangle.south; + + var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); + + carto.latitude = rectangle.north; + var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); + + carto.longitude = rectangle.east; + carto.latitude = rectangle.south; + var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); + + var attributes = {}; + addAttributesForPoint(corner, 'southWest', attributes); + addAttributesForPoint(northWest, 'northWest', attributes); + addAttributesForPoint(southEast, 'southEast', attributes); + 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. + * Used when computing texture coordinates for large-area ClassificationPrimitives with materials or + * multiple non-overlapping instances. + * @see ShadowVolumeAppearanceShader + * @private + * + * @param {Rectangle} rectangle Rectangle object that the spherical extents will approximately bound + * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + */ + ClassificationPrimitive.getSphericalExtentsGeometryInstanceAttribute = function(rectangle, ellipsoid) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + //>>includeEnd('debug'); + + // rectangle cartographic coords !== spherical because it's on an ellipsoid + var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, ellipsoid, sphericalScratch); + + // Slightly pad extents to avoid floating point error when fragment culling at edges. + var south = southWestExtents.x - CesiumMath.EPSILON5; + var west = southWestExtents.y - CesiumMath.EPSILON5; + + var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, ellipsoid, sphericalScratch); + var north = northEastExtents.x + CesiumMath.EPSILON5; + var east = northEastExtents.y + CesiumMath.EPSILON5; + + var longitudeRangeInverse = 1.0 / (east - west); + var latitudeRangeInverse = 1.0 / (north - south); + + return new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [south, west, latitudeRangeInverse, longitudeRangeInverse] + }); + }; + return ClassificationPrimitive; }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 86fa9fcd16c..31151d92590 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -17,9 +17,7 @@ define([ '../Core/Math', '../Core/OrientedBoundingBox', '../Core/Rectangle', - '../Core/ReferencePointGeometryInstanceAttribute', '../Core/Resource', - '../Core/SphericalExtentsGeometryInstanceAttribute', '../Renderer/Pass', '../ThirdParty/when', './ClassificationPrimitive', @@ -45,9 +43,7 @@ define([ CesiumMath, OrientedBoundingBox, Rectangle, - ReferencePointGeometryInstanceAttribute, Resource, - SphericalExtentsGeometryInstanceAttribute, Pass, when, ClassificationPrimitive, @@ -57,13 +53,13 @@ define([ 'use strict'; /** - * A ground primitive represents geometry draped over the terrain in the {@link Scene}. The geometry must be from a single {@link GeometryInstance}. - * Batching multiple geometries is not yet supported. + * A ground primitive represents geometry draped over the terrain in the {@link Scene}. *

- * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including + * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. + * // TODO add note that textures on ground should use single imagery provider for high precision *

*

* For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there @@ -797,10 +793,10 @@ define([ var attributes; if (usePlanarExtents) { - attributes = ReferencePointGeometryInstanceAttribute.getAttributesForPlanes(rectangle, ellipsoid, attributes); + attributes = ClassificationPrimitive.getAttributesForTextureCoordinatePlanes(rectangle, ellipsoid, attributes); } else { attributes = { - sphericalExtents : new SphericalExtentsGeometryInstanceAttribute(rectangle) + sphericalExtents : ClassificationPrimitive.getSphericalExtentsGeometryInstanceAttribute(rectangle, ellipsoid) }; } diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js index db8ffb9115f..455dbca0e44 100644 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -29,7 +29,7 @@ define([ this._extentsCulling = defaultValue(extentsCulling, false); this._planarExtents = defaultValue(planarExtents, false); this._shaderSource = createShadowVolumeAppearanceShader(appearance, this._extentsCulling, this._planarExtents); - this._usesTexcoords = shaderDependenciesScratch._requiresTexcoords; + this._usesTextureCoordinates = shaderDependenciesScratch._requiresTextureCoordinates; } defineProperties(ShadowVolumeAppearanceShader.prototype, { @@ -40,9 +40,9 @@ define([ * @type {Boolean} * @readonly */ - usesTexcoords : { + usesTextureCoordinates : { get : function() { - return this._usesTexcoords; + return this._usesTextureCoordinates; } }, /** @@ -76,8 +76,8 @@ define([ return getPerInstanceColorShader(extentsCull, appearance.flat, planarExtents); } - shaderDependencies.requiresTexcoords = extentsCull; - shaderDependencies.requiresEyeCoord = !appearance.flat; + shaderDependencies.requiresTextureCoordinates = extentsCull; + shaderDependencies.requiresEC = !appearance.flat; // Scan material source for what hookups are needed. Assume czm_materialInput materialInput. var materialShaderSource = appearance.material.shaderSource; @@ -113,10 +113,10 @@ define([ glsl += ' materialInput.normalEC = normalEC;\n'; } if (usesPositionToEyeEC) { - glsl += ' materialInput.positionToEyeEC = -eyeCoord.xyz;\n'; + glsl += ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n'; } if (usesTangentToEyeMat) { - glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoord, normalEC);\n'; + glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC);\n'; } if (usesSt) { glsl += ' materialInput.st = vec2(v, u);\n'; @@ -126,7 +126,7 @@ define([ if (appearance.flat) { glsl += ' gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n'; } else { - glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoord.xyz), material);\n'; + glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; } glsl += '}\n'; @@ -148,7 +148,7 @@ define([ 'varying vec4 v_sphericalExtents;\n'; } var shaderDependencies = shaderDependenciesScratch; - shaderDependencies.requiresTexcoords = extentsCulling; + shaderDependencies.requiresTextureCoordinates = extentsCulling; shaderDependencies.requiresNormalEC = !flatShading; glsl += getLocalFunctions(shaderDependencies, planarExtents); @@ -165,12 +165,12 @@ define([ glsl += ' czm_materialInput materialInput;\n' + ' materialInput.normalEC = normalEC;\n' + - ' materialInput.positionToEyeEC = -eyeCoord.xyz;\n' + + ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n' + ' czm_material material = czm_getDefaultMaterial(materialInput);\n' + ' material.diffuse = v_color.rgb;\n' + ' material.alpha = v_color.a;\n' + - ' gl_FragColor = czm_phong(normalize(-eyeCoord.xyz), material);\n'; + ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; } glsl += '}\n'; return glsl; @@ -178,32 +178,32 @@ define([ function getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents) { var glsl = ''; - if (shaderDependencies.requiresEyeCoord) { + if (shaderDependencies.requiresEC) { glsl += - ' vec4 eyeCoord = getEyeCoord(gl_FragCoord.xy);\n'; + ' vec4 eyeCoordinate = getEyeCoordinate(gl_FragCoord.xy);\n'; } - if (shaderDependencies.requiresWorldCoord) { + if (shaderDependencies.requiresWC) { glsl += - ' vec4 worldCoord4 = czm_inverseView * eyeCoord;\n' + - ' vec3 worldCoord = worldCoord4.xyz / worldCoord4.w;\n'; + ' vec4 worldCoordinate4 = czm_inverseView * eyeCoordinate;\n' + + ' vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;\n'; } - if (shaderDependencies.requiresTexcoords) { - if (planarExtents) { // TODO: add ability to do long-and-narrows? + if (shaderDependencies.requiresTextureCoordinates) { + if (planarExtents) { glsl += ' // Unpack planes and transform to eye space\n' + - ' float u = computePlanarTexcoord(v_southPlane, eyeCoord.xyz / eyeCoord.w, v_inversePlaneExtents.y);\n' + - ' float v = computePlanarTexcoord(v_westPlane, eyeCoord.xyz / eyeCoord.w, v_inversePlaneExtents.x);\n'; + ' float u = computePlanarTextureCoordinates(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.y);\n' + + ' float v = computePlanarTextureCoordinates(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.x);\n'; } else { glsl += ' // Treat world coords as a sphere normal for spherical coordinates\n' + - ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoord);\n' + - ' float u = (sphericalLatLong.x - v_sphericalExtents.y) * v_sphericalExtents.w;\n' + - ' float v = (sphericalLatLong.y - v_sphericalExtents.x) * v_sphericalExtents.z;\n'; // TODO: clean up... + ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate);\n' + + ' float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z;\n' + + ' float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w;\n'; } } if (extentsCulling) { glsl += - ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + // TODO: there's floating point problems at the edges of rectangles. Use remapping. + ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + ' discard;\n' + ' }\n'; } @@ -211,8 +211,8 @@ define([ if (shaderDependencies.requiresNormalEC) { glsl += ' // compute normal. sample adjacent pixels in 2x2 block in screen space\n' + - ' vec3 downUp = getVectorFromOffset(eyeCoord, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + - ' vec3 leftRight = getVectorFromOffset(eyeCoord, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + + ' vec3 downUp = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + + ' vec3 leftRight = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + ' vec3 normalEC = normalize(cross(leftRight, downUp));\n' + '\n'; } @@ -221,43 +221,43 @@ define([ function getLocalFunctions(shaderDependencies, planarExtents) { var glsl = ''; - if (shaderDependencies.requiresEyeCoord) { + if (shaderDependencies.requiresEC) { glsl += - 'vec4 getEyeCoord(vec2 fragCoord) {\n' + + 'vec4 getEyeCoordinate(vec2 fragCoord) {\n' + ' vec2 coords = fragCoord / czm_viewport.zw;\n' + ' float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + - ' vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord);\n' + - ' return eyeCoord;\n' + + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + ' return eyeCoordinate;\n' + '}\n'; } if (shaderDependencies.requiresNormalEC) { glsl += - 'vec3 getEyeCoord3FromWindowCoord(vec2 fragCoord, float depth) {\n' + + 'vec3 getEyeCoordinate3FromWindowCoordordinate(vec2 fragCoord, float depth) {\n' + ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + - ' vec4 eyeCoord = czm_windowToEyeCoordinates(windowCoord);\n' + - ' return eyeCoord.xyz / eyeCoord.w;\n' + + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + ' return eyeCoordinate.xyz / eyeCoordinate.w;\n' + '}\n' + - 'vec3 getVectorFromOffset(vec4 eyeCoord, vec2 fragCoord2, vec2 positiveOffset) {\n' + + 'vec3 getVectorFromOffset(vec4 eyeCoordinate, vec2 glFragCoordXY, vec2 positiveOffset) {\n' + ' // Sample depths at both offset and negative offset\n' + - ' float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 + positiveOffset) / czm_viewport.zw));\n' + - ' float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (fragCoord2 - positiveOffset) / czm_viewport.zw));\n' + + ' float upOrRightDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw));\n' + + ' float downOrLeftDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw));\n' + ' // Explicitly evaluate both paths\n' + - ' bvec2 upOrRightInBounds = lessThan(fragCoord2 + positiveOffset, czm_viewport.zw);\n' + + ' bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw);\n' + ' float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + - ' vec3 upOrRightEC = getEyeCoord3FromWindowCoord(fragCoord2 + positiveOffset, upOrRightDepth);\n' + - ' vec3 downOrLeftEC = getEyeCoord3FromWindowCoord(fragCoord2 - positiveOffset, downOrLeftDepth);\n' + + ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordordinate(glFragCoordXY + positiveOffset, upOrRightDepth);\n' + + ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordordinate(glFragCoordXY - positiveOffset, downOrLeftDepth);\n' + - ' return (upOrRightEC - (eyeCoord.xyz / eyeCoord.w)) * useUpOrRight + ((eyeCoord.xyz / eyeCoord.w) - downOrLeftEC) * useDownOrLeft;\n' + + ' return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft;\n' + '}\n'; } - if (shaderDependencies.requiresTexcoords && planarExtents) { + if (shaderDependencies.requiresTextureCoordinates && planarExtents) { glsl += - 'float computePlanarTexcoord(vec4 plane, vec3 eyeCoords, float inverseExtent) {\n' + - ' return (dot(plane.xyz, eyeCoords) + plane.w) * inverseExtent;\n' + + 'float computePlanarTextureCoordinates(vec4 plane, vec3 eyeCoordinates, float inverseExtent) {\n' + + ' return (dot(plane.xyz, eyeCoordinates) + plane.w) * inverseExtent;\n' + '}\n'; } return glsl; @@ -268,37 +268,37 @@ define([ * @private */ function ShaderDependencies() { - this._requiresEyeCoord = false; - this._requiresWorldCoord = false; // depends on eyeCoord, needed for material and for phong - this._requiresNormalEC = false; // depends on eyeCoord, needed for material - this._requiresTexcoords = false; // depends on worldCoord, needed for material and for culling + 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 } ShaderDependencies.prototype.reset = function() { - this._requiresEyeCoord = false; - this._requiresWorldCoord = false; + this._requiresEC = false; + this._requiresWC = false; this._requiresNormalEC = false; - this._requiresTexcoords = false; + this._requiresTextureCoordinates = false; return this; }; defineProperties(ShaderDependencies.prototype, { - // Set when assessing final shading (flat vs. phong) and spherical extent culling - requiresEyeCoord : { + // Set when assessing final shading (flat vs. phong) and culling using computed texture coordinates + requiresEC : { get : function() { - return this._requiresEyeCoord; + return this._requiresEC; }, set : function(value) { - this._requiresEyeCoord = value || this._requiresEyeCoord; + this._requiresEC = value || this._requiresEC; } }, - requiresWorldCoord : { + requiresWC : { get : function() { - return this._requiresWorldCoord; + return this._requiresWC; }, set : function(value) { - this._requiresWorldCoord = value || this._requiresWorldCoord; - this.requiresEyeCoord = this._requiresWorldCoord; + this._requiresWC = value || this._requiresWC; + this.requiresEC = this._requiresWC; } }, requiresNormalEC : { @@ -307,16 +307,16 @@ define([ }, set : function(value) { this._requiresNormalEC = value || this._requiresNormalEC; - this.requiresEyeCoord = this._requiresNormalEC; + this.requiresEC = this._requiresNormalEC; } }, - requiresTexcoords : { + requiresTextureCoordinates : { get : function() { - return this._requiresTexcoords; + return this._requiresTextureCoordinates; }, set : function(value) { - this._requiresTexcoords = value || this._requiresTexcoords; - this.requiresWorldCoord = this._requiresTexcoords; + this._requiresTextureCoordinates = value || this._requiresTextureCoordinates; + this.requiresWC = this._requiresTextureCoordinates; } }, // Set when assessing material hookups @@ -327,18 +327,18 @@ define([ }, tangentToEyeMatrix : { set : function(value) { - this.requiresWorldCoord = value; + this.requiresWC = value; this.requiresNormalEC = value; } }, positionToEyeEC : { set : function(value) { - this.requiresEyeCoord = value; + this.requiresEC = value; } }, st : { set : function(value) { - this.requiresTexcoords = value; + this.requiresTextureCoordinates = value; } } }); diff --git a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl index ace9edae9f0..90fe79393a3 100644 --- a/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/approximateSphericalCoordinates.glsl @@ -12,29 +12,22 @@ float fastApproximateAtan01(float x) { return x * (-0.1784 * x - 0.0663 * x * x + 1.0301); } -// Used in place of ternaries to trade some math for branching -float branchFree(bool comparison, float a, float b) { - float useA = float(comparison); - return a * useA + b * (1.0 - useA); -} - // Range reduction math based on nvidia's cg reference implementation for atan2: http://developer.download.nvidia.com/cg/atan2.html // However, we replaced their atan curve with Michael Drobot's. float fastApproximateAtan2(float x, float y) { // atan approximations are usually only reliable over [-1, 1], or, in our case, [0, 1] due to modifications. // So range-reduce using abs and by flipping whether x or y is on top. - float opposite, adjacent, t; // t used as swap and atan result. - t = abs(x); - opposite = abs(y); - adjacent = max(t, opposite); + float t = abs(x); // t used as swap and atan result. + float opposite = abs(y); + float adjacent = max(t, opposite); opposite = min(t, opposite); t = fastApproximateAtan01(opposite / adjacent); // Undo range reduction - t = branchFree(abs(y) > abs(x), czm_piOverTwo - t, t); - t = branchFree(x < 0.0, czm_pi - t, t); - t = branchFree(y < 0.0, -t, t); + t = czm_branchFreeTernaryFloat(abs(y) > abs(x), czm_piOverTwo - t, t); + t = czm_branchFreeTernaryFloat(x < 0.0, czm_pi - t, t); + t = czm_branchFreeTernaryFloat(y < 0.0, -t, t); return t; } diff --git a/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl b/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl new file mode 100644 index 00000000000..951f2b155ce --- /dev/null +++ b/Source/Shaders/Builtin/Functions/branchFreeTernaryFloat.glsl @@ -0,0 +1,17 @@ +/** + * Branchless ternary operator to be used when it's inexpensive to explicitly + * evaluate both possibilities for a float expression. + * + * @name czm_branchFreeTernaryFloat + * @glslFunction + * + * @param {bool} comparison A comparison statement + * @param {float} a Value to return if the comparison is true. + * @param {float} b Value to return if the comparison is false. + * + * @returns {float} equivalent of comparison ? a : b + */ +float czm_branchFreeTernaryFloat(bool comparison, float a, float b) { + float useA = float(comparison); + return a * useA + b * (1.0 - useA); +} diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index 42f97f5bca9..4138cbfd93b 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -408,4 +408,14 @@ defineSuite([ }).contextToRender(); }); + it('has czm_branchFreeTernaryFloat', function() { + var fs = + 'void main() { ' + + ' gl_FragColor = vec4(czm_branchFreeTernaryFloat(true, 1.0, 0.0));' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); }, 'WebGL'); From 8e59e2d93ca5e21d0c7785605a831ec85baafc39 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 11 Apr 2018 14:00:34 -0400 Subject: [PATCH 16/40] add texture coordinate rotation for materials on GroundPrimitives --- Source/Scene/ClassificationPrimitive.js | 115 +++++++++++++++++-- Source/Scene/GroundPrimitive.js | 6 +- Source/Scene/ShadowVolumeAppearanceShader.js | 11 +- Source/Shaders/ShadowVolumeVS.glsl | 4 + 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 9ac42e6a9fb..ed855f81e68 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -15,6 +15,7 @@ define([ '../Core/EncodedCartesian3', '../Core/GeometryInstance', '../Core/GeometryInstanceAttribute', + '../Core/Matrix2', '../Core/Rectangle', '../Core/WebGLConstants', '../Core/isArray', @@ -54,6 +55,7 @@ define([ EncodedCartesian3, GeometryInstance, GeometryInstanceAttribute, + Matrix2, Rectangle, WebGLConstants, isArray, @@ -1158,6 +1160,92 @@ define([ } var cartographicScratch = new Cartographic(); + var rectangleCenterScratch = new Cartographic(); + var northCenterScratch = new Cartesian3(); + var southCenterScratch = new Cartesian3(); + var eastCenterScratch = new Cartesian3(); + var westCenterScratch = new Cartesian3(); + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; + var rotation2DScratch = new Matrix2(); + var min2DScratch = new Cartesian2(); + var max2DScratch = new Cartesian2(); + function getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) { + var theta = defaultValue(textureCoordinateRotation, 0.0); + + // Approximate scale such that the rectangle, if scaled and rotated, will completely enclose + // the unrotated/unscaled rectangle. + var cosTheta = Math.cos(theta); + var sinTheta = Math.sin(theta); + + // Build a rectangle centered in 2D space approximating the bounding rectangle's dimensions + var cartoCenter = Rectangle.center(rectangle, rectangleCenterScratch); + + var carto = cartographicScratch; + carto.latitude = cartoCenter.latitude; + + carto.longitude = rectangle.west; + var westCenter = Cartographic.toCartesian(carto, ellipsoid, westCenterScratch); + + carto.longitude = rectangle.east; + var eastCenter = Cartographic.toCartesian(carto, ellipsoid, eastCenterScratch); + + carto.longitude = cartoCenter.longitude; + carto.latitude = rectangle.north; + var northCenter = Cartographic.toCartesian(carto, ellipsoid, northCenterScratch); + + carto.latitude = rectangle.south; + var southCenter = Cartographic.toCartesian(carto, ellipsoid, southCenterScratch); + + var northSouthHalfDistance = Cartesian3.distance(northCenter, southCenter) * 0.5; + var eastWestHalfDistance = Cartesian3.distance(eastCenter, westCenter) * 0.5; + + var points2D = points2DScratch; + points2D[0].x = eastWestHalfDistance; + points2D[0].y = northSouthHalfDistance; + + points2D[1].x = -eastWestHalfDistance; + points2D[1].y = northSouthHalfDistance; + + points2D[2].x = eastWestHalfDistance; + points2D[2].y = -northSouthHalfDistance; + + points2D[3].x = -eastWestHalfDistance; + points2D[3].y = -northSouthHalfDistance; + + // Rotate the dimensions rectangle and compute min/max in rotated space + var min2D = min2DScratch; + min2D.x = Number.POSITIVE_INFINITY; + min2D.y = Number.POSITIVE_INFINITY; + var max2D = max2DScratch; + max2D.x = Number.NEGATIVE_INFINITY; + max2D.y = Number.NEGATIVE_INFINITY; + + var rotation2D = Matrix2.fromRotation(-theta, rotation2DScratch); + for (var i = 0; i < 4; ++i) { + var point2D = points2D[i]; + Matrix2.multiplyByVector(rotation2D, point2D, point2D); + Cartesian2.minimumByComponent(point2D, min2D, min2D); + Cartesian2.maximumByComponent(point2D, max2D, max2D); + } + + // Depending on the rotation, east/west may be more appropriate for vertical scale than horizontal + var scaleU, scaleV; + if (Math.abs(sinTheta) < Math.abs(cosTheta)) { + scaleU = eastWestHalfDistance / ((max2D.x - min2D.x) * 0.5); + scaleV = northSouthHalfDistance / ((max2D.y - min2D.y) * 0.5); + } else { + scaleU = eastWestHalfDistance / ((max2D.y - min2D.y) * 0.5); + scaleV = northSouthHalfDistance / ((max2D.x - min2D.x) * 0.5); + } + + return new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [sinTheta, cosTheta, scaleU, scaleV] // Precompute trigonometry for rotation and inverse of scale + }); + } + var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); @@ -1170,8 +1258,10 @@ define([ * * @param {Rectangle} rectangle Rectangle object that the points will approximately bound * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @return {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ClassificationPrimitive.getAttributesForTextureCoordinatePlanes = function(rectangle, ellipsoid) { + ClassificationPrimitive.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('rectangle', rectangle); Check.typeOf.object('ellipsoid', ellipsoid); @@ -1191,7 +1281,9 @@ define([ carto.latitude = rectangle.south; var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); - var attributes = {}; + var attributes = { + stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) + }; addAttributesForPoint(corner, 'southWest', attributes); addAttributesForPoint(northWest, 'northWest', attributes); addAttributesForPoint(southEast, 'southEast', attributes); @@ -1232,8 +1324,10 @@ define([ * * @param {Rectangle} rectangle Rectangle object that the spherical extents will approximately bound * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @return An attributes dictionary containing spherical texture coordinate attributes. */ - ClassificationPrimitive.getSphericalExtentsGeometryInstanceAttribute = function(rectangle, ellipsoid) { + ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('rectangle', rectangle); Check.typeOf.object('ellipsoid', ellipsoid); @@ -1253,12 +1347,15 @@ define([ var longitudeRangeInverse = 1.0 / (east - west); var latitudeRangeInverse = 1.0 / (north - south); - return new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : [south, west, latitudeRangeInverse, longitudeRangeInverse] - }); + return { + sphericalExtents : new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [south, west, latitudeRangeInverse, longitudeRangeInverse] + }), + stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) + }; }; return ClassificationPrimitive; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 31151d92590..efdf8714e3f 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -793,11 +793,9 @@ define([ var attributes; if (usePlanarExtents) { - attributes = ClassificationPrimitive.getAttributesForTextureCoordinatePlanes(rectangle, ellipsoid, attributes); + attributes = ClassificationPrimitive.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, geometry._stRotation); } else { - attributes = { - sphericalExtents : ClassificationPrimitive.getSphericalExtentsGeometryInstanceAttribute(rectangle, ellipsoid) - }; + attributes = ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, geometry._stRotation); } var instanceAttributes = instance.attributes; diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js index 455dbca0e44..863bb48fab9 100644 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -99,6 +99,10 @@ define([ 'varying vec4 v_sphericalExtents;\n'; } + if (usesSt) { + glsl += + 'varying vec4 v_stSineCosineUVScale;\n'; + } glsl += getLocalFunctions(shaderDependencies, planarExtents); @@ -119,7 +123,10 @@ define([ glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC);\n'; } if (usesSt) { - glsl += ' materialInput.st = vec2(v, u);\n'; + // Scale texture coordinates and rotate around 0.5, 0.5 + glsl += + ' materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5;\n' + + ' materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5;\n'; } glsl += ' czm_material material = czm_getMaterial(materialInput);\n'; @@ -345,3 +352,5 @@ define([ return ShadowVolumeAppearanceShader; }); + +// TODO: have this inject code into ShadowVolumeVS so that's less messy diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 55a0c9e1262..91ea1dd23b5 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -20,12 +20,14 @@ uniform float u_globeMinimumAltitude; #ifdef SPHERICAL_EXTENTS varying vec4 v_sphericalExtents; +varying vec4 v_stSineCosineUVScale; #endif #ifdef PLANAR_EXTENTS varying vec2 v_inversePlaneExtents; varying vec4 v_westPlane; varying vec4 v_southPlane; +varying vec4 v_stSineCosineUVScale; #endif #endif @@ -46,6 +48,7 @@ void main() #ifdef SPHERICAL_EXTENTS v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); + v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); #endif #ifdef PLANAR_EXTENTS @@ -64,6 +67,7 @@ void main() v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); + v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); #endif vec4 position = czm_computePosition(); From 5ee2f0715d75b2c003b01d15a310d5cf29f263ef Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 12 Apr 2018 11:21:29 -0400 Subject: [PATCH 17/40] added GroundPrimitives material support for 2D/CV [ci skip] --- Source/Scene/ClassificationPrimitive.js | 88 ++++++++++++++++++++++--- Source/Scene/GroundPrimitive.js | 19 +++++- Source/Shaders/ShadowVolumeVS.glsl | 26 ++++++++ 3 files changed, 122 insertions(+), 11 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index ed855f81e68..fe8081d17ec 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -197,6 +197,7 @@ define([ this._spStencil = undefined; this._spPick = undefined; this._spColor = undefined; + this._spColor2D = undefined; this._rsStencilPreloadPass = undefined; this._rsStencilDepthPass = undefined; @@ -668,10 +669,10 @@ define([ var isPerInstanceColor = appearance instanceof PerInstanceColorAppearance; var parts; - - // Create a fragment shader that computes only required material hookups using screen space techniques var usePlanarExtents = classificationPrimitive._hasPlanarExtentsAttributes; var cullUsingExtents = classificationPrimitive._hasPlanarExtentsAttributes || classificationPrimitive._hasSphericalExtentsAttribute; + + // Create a fragment shader that computes only required material hookups using screen space techniques var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(appearance, cullUsingExtents, usePlanarExtents); var shadowVolumeAppearanceFS = shadowVolumeAppearanceShader.fragmentShaderSource; if (isPerInstanceColor) { @@ -680,6 +681,10 @@ define([ parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS]; } + var fsColorSource = new ShaderSource({ + sources : [parts.join('\n')] + }); + var colorVSDefines = isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : []; if (shadowVolumeAppearanceShader.usesTextureCoordinates) { if (shadowVolumeAppearanceShader.planarExtents) { @@ -693,10 +698,6 @@ define([ sources : [vs] }); - var fsColorSource = new ShaderSource({ - sources : [parts.join('\n')] - }); - classificationPrimitive._spColor = ShaderProgram.replaceCache({ context : context, shaderProgram : classificationPrimitive._spColor, @@ -704,6 +705,32 @@ define([ fragmentShaderSource : fsColorSource, attributeLocations : attributeLocations }); + + // Create a similar fragment shader for 2D, forcing planar extents + var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(appearance, cullUsingExtents, true); + var shadowVolumeAppearanceFS2D = shadowVolumeAppearanceShader2D.fragmentShaderSource; + if (isPerInstanceColor) { + parts = [shadowVolumeAppearanceFS2D]; + } else { + parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS2D]; + } + + var fsColorSource2D = new ShaderSource({ + sources : [parts.join('\n')] + }); + + var vsColorSource2D = new ShaderSource({ + defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR', 'COLUMBUS_VIEW_2D'] : ['COLUMBUS_VIEW_2D'], + sources : [vs] + }); + + classificationPrimitive._spColor2D = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spColor2D, + vertexShaderSource : vsColorSource2D, + fragmentShaderSource : fsColorSource2D, + attributeLocations : attributeLocations + }); } function createColorCommands(classificationPrimitive, colorCommands) { @@ -1131,6 +1158,7 @@ define([ this._sp = this._sp && this._sp.destroy(); this._spPick = this._spPick && this._spPick.destroy(); this._spColor = this._spColor && this._spColor.destroy(); + this._spColor2D = this._spColor2D && this._spColor2D.destroy(); return destroyObject(this); }; @@ -1246,9 +1274,42 @@ define([ }); } + var swizzleScratch = new Cartesian3(); + function swizzle(cartesian) { + Cartesian3.clone(cartesian, swizzleScratch); + cartesian.x = swizzleScratch.z; + cartesian.y = swizzleScratch.x; + cartesian.z = swizzleScratch.y; + return cartesian; + } + var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); + function add2DTextureCoordinateAttributes(rectangle, frameState, attributes) { + var projection = frameState.mapProjection; + + // Compute corner positions in double precision + var carto = cartographicScratch; + carto.height = 0.0; + + carto.longitude = rectangle.west; + carto.latitude = rectangle.south; + + var corner = swizzle(projection.project(carto, cornerScratch)); + + carto.latitude = rectangle.north; + var northWest = swizzle(projection.project(carto, northWestScratch)); + + carto.longitude = rectangle.east; + carto.latitude = rectangle.south; + var southEast = swizzle(projection.project(carto, southEastScratch)); + + addAttributesForPoint(corner, 'southWest2D', attributes); + addAttributesForPoint(northWest, 'northWest2D', attributes); + addAttributesForPoint(southEast, 'southEast2D', attributes); + } + /** * Gets an attributes object containing 3 high-precision points as 6 GeometryInstanceAttributes. * These points are used to compute eye-space planes, which are then used to compute texture @@ -1261,14 +1322,17 @@ define([ * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation * @return {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ClassificationPrimitive.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, textureCoordinateRotation) { + ClassificationPrimitive.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, frameState, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('rectangle', rectangle); Check.typeOf.object('ellipsoid', ellipsoid); + Check.typeOf.object('frameState', frameState); //>>includeEnd('debug'); // Compute corner positions in double precision var carto = cartographicScratch; + carto.height = 0.0; + carto.longitude = rectangle.west; carto.latitude = rectangle.south; @@ -1287,6 +1351,8 @@ define([ addAttributesForPoint(corner, 'southWest', attributes); addAttributesForPoint(northWest, 'northWest', attributes); addAttributesForPoint(southEast, 'southEast', attributes); + + add2DTextureCoordinateAttributes(rectangle, frameState, attributes); return attributes; }; @@ -1327,10 +1393,11 @@ define([ * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation * @return An attributes dictionary containing spherical texture coordinate attributes. */ - ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, textureCoordinateRotation) { + ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, frameState, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('rectangle', rectangle); Check.typeOf.object('ellipsoid', ellipsoid); + Check.typeOf.object('frameState', frameState); //>>includeEnd('debug'); // rectangle cartographic coords !== spherical because it's on an ellipsoid @@ -1347,7 +1414,7 @@ define([ var longitudeRangeInverse = 1.0 / (east - west); var latitudeRangeInverse = 1.0 / (north - south); - return { + var attributes = { sphericalExtents : new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.FLOAT, componentsPerAttribute: 4, @@ -1356,6 +1423,9 @@ define([ }), stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) }; + + add2DTextureCoordinateAttributes(rectangle, frameState, attributes); + return attributes; }; return ClassificationPrimitive; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index efdf8714e3f..18e46255ede 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -18,6 +18,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/Rectangle', '../Core/Resource', + '../Renderer/DrawCommand', '../Renderer/Pass', '../ThirdParty/when', './ClassificationPrimitive', @@ -44,6 +45,7 @@ define([ OrientedBoundingBox, Rectangle, Resource, + DrawCommand, Pass, when, ClassificationPrimitive, @@ -623,6 +625,7 @@ define([ var colorLength = colorCommands.length; var i; var colorCommand; + var classificationPrimitive = groundPrimitive._classificationPrimitive; for (i = 0; i < colorLength; ++i) { colorCommand = colorCommands[i]; @@ -633,6 +636,18 @@ define([ colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; colorCommand.pass = pass; + // derive a separate appearance command for 2D + if (frameState.mode !== SceneMode.SCENE3D && + colorCommand.shaderProgram === classificationPrimitive._spColor) { + var derivedCommand = colorCommand.derivedCommands.appearance2D; + if (!defined(derivedCommand)) { + derivedCommand = DrawCommand.shallowClone(colorCommand); + derivedCommand.shaderProgram = classificationPrimitive._spColor2D; + colorCommand.derivedCommands.appearance2D = derivedCommand; + } + colorCommand = derivedCommand; + } + commandList.push(colorCommand); } @@ -793,9 +808,9 @@ define([ var attributes; if (usePlanarExtents) { - attributes = ClassificationPrimitive.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, geometry._stRotation); + attributes = ClassificationPrimitive.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState, geometry._stRotation); } else { - attributes = ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, geometry._stRotation); + attributes = ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState, geometry._stRotation); } var instanceAttributes = instance.attributes; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 91ea1dd23b5..e36d597c0c7 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -30,6 +30,13 @@ varying vec4 v_southPlane; varying vec4 v_stSineCosineUVScale; #endif +#ifdef COLUMBUS_VIEW_2D // ugh... okay this really needs to become more programmatic +varying vec2 v_inversePlaneExtents; +varying vec4 v_westPlane; +varying vec4 v_southPlane; +varying vec4 v_stSineCosineUVScale; +#endif + #endif #ifdef PER_INSTANCE_COLOR @@ -70,6 +77,25 @@ void main() v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); #endif +#ifdef COLUMBUS_VIEW_2D + vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest2D_HIGH(batchId), czm_batchTable_southWest2D_LOW(batchId))).xyz; + vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest2D_HIGH(batchId), czm_batchTable_northWest2D_LOW(batchId))).xyz; + vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast2D_HIGH(batchId), czm_batchTable_southEast2D_LOW(batchId))).xyz; + + vec3 eastWard = southEastCorner - southWestCorner; + float eastExtent = length(eastWard); + eastWard /= eastExtent; + + vec3 northWard = northWestCorner - southWestCorner; + float northExtent = length(northWard); + northWard /= northExtent; + + v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); + v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); + v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); + v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); +#endif + vec4 position = czm_computePosition(); #ifdef EXTRUDED_GEOMETRY From 849c8a67a45f637fdc196ba74daf80f3bece45e5 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 12 Apr 2018 13:56:08 -0400 Subject: [PATCH 18/40] reduce batch table use for 2D texcoords on GroundPrimitives [ci skip] --- Source/Scene/ClassificationPrimitive.js | 47 ++++++++++++++++++++++--- Source/Shaders/ShadowVolumeVS.glsl | 9 +++-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index fe8081d17ec..b64c7e9983e 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -1274,6 +1274,9 @@ define([ }); } + // Swizzle to 2D/CV projected space. This produces positions that + // can directly be used in the VS for 2D/CV, but in practice there's + // a lot of duplicated and zero values (see below). var swizzleScratch = new Cartesian3(); function swizzle(cartesian) { Cartesian3.clone(cartesian, swizzleScratch); @@ -1286,6 +1289,7 @@ define([ var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); + var highLowScratch = {high : 0.0, low : 0.0}; function add2DTextureCoordinateAttributes(rectangle, frameState, attributes) { var projection = frameState.mapProjection; @@ -1296,7 +1300,7 @@ define([ carto.longitude = rectangle.west; carto.latitude = rectangle.south; - var corner = swizzle(projection.project(carto, cornerScratch)); + var southWestCorner = swizzle(projection.project(carto, cornerScratch)); carto.latitude = rectangle.north; var northWest = swizzle(projection.project(carto, northWestScratch)); @@ -1305,9 +1309,44 @@ define([ carto.latitude = rectangle.south; var southEast = swizzle(projection.project(carto, southEastScratch)); - addAttributesForPoint(corner, 'southWest2D', attributes); - addAttributesForPoint(northWest, 'northWest2D', attributes); - addAttributesForPoint(southEast, 'southEast2D', attributes); + // 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: y value for southWestCorner + // y: z value for southWestCorner + // z: z value for northWest + // w: y value for southEast + var valuesHigh = [0, 0, 0, 0]; + var valuesLow = [0, 0, 0, 0]; + var encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); + valuesHigh[0] = encoded.high; + valuesLow[0] = encoded.low; + + encoded = EncodedCartesian3.encode(southWestCorner.z, highLowScratch); + valuesHigh[1] = encoded.high; + valuesLow[1] = encoded.low; + + encoded = EncodedCartesian3.encode(northWest.z, highLowScratch); + valuesHigh[2] = encoded.high; + valuesLow[2] = encoded.low; + + encoded = EncodedCartesian3.encode(southEast.y, 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 + }); } /** diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index e36d597c0c7..eefefe2b3d4 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -78,9 +78,12 @@ void main() #endif #ifdef COLUMBUS_VIEW_2D - vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest2D_HIGH(batchId), czm_batchTable_southWest2D_LOW(batchId))).xyz; - vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest2D_HIGH(batchId), czm_batchTable_northWest2D_LOW(batchId))).xyz; - vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast2D_HIGH(batchId), czm_batchTable_southEast2D_LOW(batchId))).xyz; + vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId); + vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId); + + vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz; + vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz; + 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; vec3 eastWard = southEastCorner - southWestCorner; float eastExtent = length(eastWard); From 6b43300da4696082d216dbd0a67e7766a0cf0bcd Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 19 Apr 2018 18:14:03 -0400 Subject: [PATCH 19/40] add batched drawing for pick [ci skip] --- Source/Scene/ClassificationPrimitive.js | 77 ++++++++++++-------- Source/Scene/GroundPrimitive.js | 35 +++++---- Source/Scene/Scene.js | 3 +- Source/Scene/ShadowVolumeAppearanceShader.js | 63 +++++++++++++--- 4 files changed, 125 insertions(+), 53 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index b64c7e9983e..3a242464613 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -196,6 +196,7 @@ define([ this._sp = undefined; this._spStencil = undefined; this._spPick = undefined; + this._spPick2D = undefined; this._spColor = undefined; this._spColor2D = undefined; @@ -597,6 +598,9 @@ define([ vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); vs = Primitive._updateColorAttribute(primitive, vs); + var usePlanarExtents = classificationPrimitive._hasPlanarExtentsAttributes; + var cullUsingExtents = classificationPrimitive._hasPlanarExtentsAttributes || classificationPrimitive._hasSphericalExtentsAttribute; + if (classificationPrimitive._extruded) { vs = modifyForEncodedNormals(primitive, vs); } @@ -625,21 +629,48 @@ define([ vsPick = Primitive._appendShowToShader(primitive, vsPick); vsPick = Primitive._updatePickColorAttribute(vsPick); - var pickVS = new ShaderSource({ - defines : [extrudedDefine], - sources : [vsPick] + var pick3DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents); + var pickFS3D = new ShaderSource({ + sources : [pick3DShadowVolumeAppearanceShader.fragmentShaderSource], + pickColorQualifier : 'varying' }); - var pickFS = new ShaderSource({ - sources : [ShadowVolumeFS], - pickColorQualifier : 'varying' + var pickVSDefines = [extrudedDefine]; + if (pick3DShadowVolumeAppearanceShader.planarExtents) { + pickVSDefines.push('PLANAR_EXTENTS'); + } else { + pickVSDefines.push('SPHERICAL_EXTENTS'); + } + + var pickVS3D = new ShaderSource({ + defines : pickVSDefines, + sources : [vsPick] }); classificationPrimitive._spPick = ShaderProgram.replaceCache({ context : context, shaderProgram : classificationPrimitive._spPick, - vertexShaderSource : pickVS, - fragmentShaderSource : pickFS, + vertexShaderSource : pickVS3D, + fragmentShaderSource : pickFS3D, + attributeLocations : attributeLocations + }); + + var pickVS2D = new ShaderSource({ + defines : [extrudedDefine, 'COLUMBUS_VIEW_2D'], + sources : [vsPick] + }); + + var pick2DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, true); + var pickFS2D = new ShaderSource({ + sources : [pick2DShadowVolumeAppearanceShader.fragmentShaderSource], + pickColorQualifier : 'varying' + }); + + classificationPrimitive._spPick2D = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spPick2D, + vertexShaderSource : pickVS2D, + fragmentShaderSource : pickFS2D, attributeLocations : attributeLocations }); } else { @@ -669,11 +700,9 @@ define([ var isPerInstanceColor = appearance instanceof PerInstanceColorAppearance; var parts; - var usePlanarExtents = classificationPrimitive._hasPlanarExtentsAttributes; - var cullUsingExtents = classificationPrimitive._hasPlanarExtentsAttributes || classificationPrimitive._hasSphericalExtentsAttribute; // Create a fragment shader that computes only required material hookups using screen space techniques - var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(appearance, cullUsingExtents, usePlanarExtents); + var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, appearance); var shadowVolumeAppearanceFS = shadowVolumeAppearanceShader.fragmentShaderSource; if (isPerInstanceColor) { parts = [shadowVolumeAppearanceFS]; @@ -707,7 +736,7 @@ define([ }); // Create a similar fragment shader for 2D, forcing planar extents - var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(appearance, cullUsingExtents, true); + var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(cullUsingExtents, true, appearance); var shadowVolumeAppearanceFS2D = shadowVolumeAppearanceShader2D.fragmentShaderSource; if (isPerInstanceColor) { parts = [shadowVolumeAppearanceFS2D]; @@ -817,21 +846,16 @@ define([ function createPickCommands(classificationPrimitive, pickCommands) { var primitive = classificationPrimitive._primitive; - var pickOffsets = primitive._pickOffsets; - var length = pickOffsets.length * 3; + var length = primitive._va.length * 3; // each geometry (pack of vertex attributes) needs 3 commands: front/back stencils and fill pickCommands.length = length; var j; var command; - var pickIndex = 0; + var vaIndex = 0; var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); for (j = 0; j < length; j += 3) { - var pickOffset = pickOffsets[pickIndex++]; - - var offset = pickOffset.offset; - var count = pickOffset.count; - var vertexArray = primitive._va[pickOffset.index]; + var vertexArray = primitive._va[vaIndex++]; // stencil preload command command = pickCommands[j]; @@ -843,10 +867,8 @@ define([ } command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; command.renderState = classificationPrimitive._rsStencilPreloadPass; - command.shaderProgram = classificationPrimitive._spStencil; + command.shaderProgram = classificationPrimitive._sp; command.uniformMap = uniformMap; // stencil depth command @@ -859,13 +881,11 @@ define([ } command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; command.renderState = classificationPrimitive._rsStencilDepthPass; - command.shaderProgram = classificationPrimitive._spStencil; + command.shaderProgram = classificationPrimitive._sp; command.uniformMap = uniformMap; - // color command + // pick color command command = pickCommands[j + 2]; if (!defined(command)) { command = pickCommands[j + 2] = new DrawCommand({ @@ -875,8 +895,6 @@ define([ } command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; command.renderState = classificationPrimitive._rsPickPass; command.shaderProgram = classificationPrimitive._spPick; command.uniformMap = uniformMap; @@ -1157,6 +1175,7 @@ define([ this._primitive = this._primitive && this._primitive.destroy(); this._sp = this._sp && this._sp.destroy(); this._spPick = this._spPick && this._spPick.destroy(); + this._spPick2D = this._spPick2D && this._spPick2D.destroy(); this._spColor = this._spColor && this._spColor.destroy(); this._spColor2D = this._spColor2D && this._spColor2D.destroy(); return destroyObject(this); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 18e46255ede..67f9d5206b0 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -621,11 +621,11 @@ define([ var commandList = frameState.commandList; var passes = frameState.passes; + var classificationPrimitive = groundPrimitive._classificationPrimitive; if (passes.render) { var colorLength = colorCommands.length; var i; var colorCommand; - var classificationPrimitive = groundPrimitive._classificationPrimitive; for (i = 0; i < colorLength; ++i) { colorCommand = colorCommands[i]; @@ -639,20 +639,20 @@ define([ // derive a separate appearance command for 2D if (frameState.mode !== SceneMode.SCENE3D && colorCommand.shaderProgram === classificationPrimitive._spColor) { - var derivedCommand = colorCommand.derivedCommands.appearance2D; - if (!defined(derivedCommand)) { - derivedCommand = DrawCommand.shallowClone(colorCommand); - derivedCommand.shaderProgram = classificationPrimitive._spColor2D; - colorCommand.derivedCommands.appearance2D = derivedCommand; + var derivedColorCommand = colorCommand.derivedCommands.appearance2D; + if (!defined(derivedColorCommand)) { + derivedColorCommand = DrawCommand.shallowClone(colorCommand); + derivedColorCommand.shaderProgram = classificationPrimitive._spColor2D; + colorCommand.derivedCommands.appearance2D = derivedColorCommand; } - colorCommand = derivedCommand; + colorCommand = derivedColorCommand; } commandList.push(colorCommand); } if (frameState.invertClassification) { - var ignoreShowCommands = groundPrimitive._classificationPrimitive._commandsIgnoreShow; + var ignoreShowCommands = classificationPrimitive._commandsIgnoreShow; var ignoreShowCommandsLength = ignoreShowCommands.length; for (i = 0; i < ignoreShowCommandsLength; ++i) { @@ -670,19 +670,26 @@ define([ if (passes.pick) { var pickLength = pickCommands.length; - var primitive = groundPrimitive._classificationPrimitive._primitive; - var pickOffsets = primitive._pickOffsets; for (var j = 0; j < pickLength; ++j) { - var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)]; - var bv = boundingVolumes[pickOffset.index]; - var pickCommand = pickCommands[j]; + pickCommand.owner = groundPrimitive; pickCommand.modelMatrix = modelMatrix; - pickCommand.boundingVolume = bv; + pickCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(j, pickLength)]; pickCommand.cull = cull; pickCommand.pass = pass; + // derive a separate appearance command for 2D + if (frameState.mode !== SceneMode.SCENE3D && + pickCommand.shaderProgram === classificationPrimitive._spPick) { + var derivedPickCommand = pickCommand.derivedCommands.pick2D; + if (!defined(derivedPickCommand)) { + derivedPickCommand = DrawCommand.shallowClone(pickCommand); + derivedPickCommand.shaderProgram = classificationPrimitive._spPick2D; + pickCommand.derivedCommands.pick2D = derivedPickCommand; + } + pickCommand = derivedPickCommand; + } commandList.push(pickCommand); } } diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 5ba3ee1a772..425c0058d90 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -2787,7 +2787,8 @@ define([ clear.execute(context, passState); // Update globe depth rendering based on the current context and clear the globe depth framebuffer. - var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = !picking && defined(scene._globeDepth); + // Globe depth needs is copied for Pick to support picking batched geometries in GroundPrimitives. + var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = defined(scene._globeDepth); if (useGlobeDepthFramebuffer) { scene._globeDepth.update(context, passState); scene._globeDepth.clear(context, passState, clearColor); diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js index 863bb48fab9..529b81dbaba 100644 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -1,12 +1,14 @@ define([ '../Core/Check', '../Core/defaultValue', + '../Core/defined', '../Core/defineProperties', '../Renderer/PixelDatatype', '../Scene/PerInstanceColorAppearance' ], function( Check, defaultValue, + defined, defineProperties, PixelDatatype, PerInstanceColorAppearance) { @@ -16,18 +18,20 @@ define([ /** * Creates the shadow volume fragment shader for a ClassificationPrimitive to use a given appearance. * - * @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive. - * @param {Boolean} [extentsCulling=false] Discard fragments outside the instance's spherical extents. + * @param {Boolean} extentsCulling Discard fragments outside the instance's spherical extents. + * @param {Boolean} planarExtents + * @param {Appearance} [appearance] An Appearance to be used with a ClassificationPrimitive. Leave undefined for picking. * @returns {String} Shader source for a fragment shader using the input appearance. * @private */ - function ShadowVolumeAppearanceShader(appearance, extentsCulling, planarExtents) { + function ShadowVolumeAppearanceShader(extentsCulling, planarExtents, appearance) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('appearance', appearance); + Check.typeOf.bool('extentsCulling', extentsCulling); + Check.typeOf.bool('planarExtents', planarExtents); //>>includeEnd('debug'); - this._extentsCulling = defaultValue(extentsCulling, false); - this._planarExtents = defaultValue(planarExtents, false); + this._extentsCulling = extentsCulling; + this._planarExtents = planarExtents; this._shaderSource = createShadowVolumeAppearanceShader(appearance, this._extentsCulling, this._planarExtents); this._usesTextureCoordinates = shaderDependenciesScratch._requiresTextureCoordinates; } @@ -72,6 +76,9 @@ define([ function createShadowVolumeAppearanceShader(appearance, extentsCull, planarExtents) { var shaderDependencies = shaderDependenciesScratch.reset(); + if (!defined(appearance)) { + return getColorlessShader(extentsCull, planarExtents); + } if (appearance instanceof PerInstanceColorAppearance) { return getPerInstanceColorShader(extentsCull, appearance.flat, planarExtents); } @@ -135,11 +142,44 @@ define([ } else { glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; } - + glsl += ' czm_writeDepthClampedToFarPlane();\n'; glsl += '}\n'; return glsl; } + function getColorlessShader(extentsCulling, planarExtents) { + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n'; + if (extentsCulling) { + glsl += planarExtents ? + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' : + + 'varying vec4 v_sphericalExtents;\n'; + } + var shaderDependencies = shaderDependenciesScratch; + shaderDependencies.requiresTextureCoordinates = extentsCulling; + shaderDependencies.requiresNormalEC = false; + + glsl += getLocalFunctions(shaderDependencies, planarExtents); + + glsl += 'void main(void)\n' + + '{\n'; + glsl += ' bool culled = false;\n'; + var outOfBoundsSnippet = + ' culled = true;\n'; + glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); + glsl += ' if (!culled) {\n' + + ' gl_FragColor.a = 1.0;\n' + // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource + ' czm_writeDepthClampedToFarPlane();\n' + + ' }\n' + + '}\n'; + return glsl; + } + function getPerInstanceColorShader(extentsCulling, flatShading, planarExtents) { var glsl = '#ifdef GL_EXT_frag_depth\n' + @@ -179,11 +219,12 @@ define([ ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; } + glsl += ' czm_writeDepthClampedToFarPlane();\n'; glsl += '}\n'; return glsl; } - function getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents) { + function getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { var glsl = ''; if (shaderDependencies.requiresEC) { glsl += @@ -209,9 +250,13 @@ define([ } } if (extentsCulling) { + if (!defined(outOfBoundsSnippet)) { + outOfBoundsSnippet = + ' discard;\n'; + } glsl += ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + - ' discard;\n' + + outOfBoundsSnippet + ' }\n'; } // Lots of texture access, so lookup after discard check From de2268a547baed6bf3c6a9bb3c44fd1dc252e83c Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 20 Apr 2018 11:03:09 -0400 Subject: [PATCH 20/40] cleanup for shadow volume material vertex shaders [ci skip] --- Source/Scene/ClassificationPrimitive.js | 45 ++---- Source/Scene/ShadowVolumeAppearanceShader.js | 152 +++++++++++++------ Source/Shaders/ShadowVolumeFS.glsl | 2 - Source/Shaders/ShadowVolumeVS.glsl | 80 ---------- 4 files changed, 124 insertions(+), 155 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 3a242464613..6e8a65c36cb 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -629,22 +629,15 @@ define([ vsPick = Primitive._appendShowToShader(primitive, vsPick); vsPick = Primitive._updatePickColorAttribute(vsPick); - var pick3DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents); + var pick3DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, false, vsPick); var pickFS3D = new ShaderSource({ sources : [pick3DShadowVolumeAppearanceShader.fragmentShaderSource], pickColorQualifier : 'varying' }); - var pickVSDefines = [extrudedDefine]; - if (pick3DShadowVolumeAppearanceShader.planarExtents) { - pickVSDefines.push('PLANAR_EXTENTS'); - } else { - pickVSDefines.push('SPHERICAL_EXTENTS'); - } - var pickVS3D = new ShaderSource({ - defines : pickVSDefines, - sources : [vsPick] + defines : [extrudedDefine], + sources : [pick3DShadowVolumeAppearanceShader.vertexShaderSource] }); classificationPrimitive._spPick = ShaderProgram.replaceCache({ @@ -655,17 +648,17 @@ define([ attributeLocations : attributeLocations }); - var pickVS2D = new ShaderSource({ - defines : [extrudedDefine, 'COLUMBUS_VIEW_2D'], - sources : [vsPick] - }); - - var pick2DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, true); + var pick2DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, true, true, vsPick); var pickFS2D = new ShaderSource({ sources : [pick2DShadowVolumeAppearanceShader.fragmentShaderSource], pickColorQualifier : 'varying' }); + var pickVS2D = new ShaderSource({ + defines : [extrudedDefine], + sources : [pick2DShadowVolumeAppearanceShader.vertexShaderSource] + }); + classificationPrimitive._spPick2D = ShaderProgram.replaceCache({ context : context, shaderProgram : classificationPrimitive._spPick2D, @@ -702,7 +695,7 @@ define([ var parts; // Create a fragment shader that computes only required material hookups using screen space techniques - var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, appearance); + var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, false, vs, appearance); var shadowVolumeAppearanceFS = shadowVolumeAppearanceShader.fragmentShaderSource; if (isPerInstanceColor) { parts = [shadowVolumeAppearanceFS]; @@ -714,17 +707,9 @@ define([ sources : [parts.join('\n')] }); - var colorVSDefines = isPerInstanceColor ? ['PER_INSTANCE_COLOR'] : []; - if (shadowVolumeAppearanceShader.usesTextureCoordinates) { - if (shadowVolumeAppearanceShader.planarExtents) { - colorVSDefines.push('PLANAR_EXTENTS'); - } else { - colorVSDefines.push('SPHERICAL_EXTENTS'); - } - } var vsColorSource = new ShaderSource({ - defines : colorVSDefines, - sources : [vs] + defines : [extrudedDefine], + sources : [shadowVolumeAppearanceShader.vertexShaderSource] }); classificationPrimitive._spColor = ShaderProgram.replaceCache({ @@ -736,7 +721,7 @@ define([ }); // Create a similar fragment shader for 2D, forcing planar extents - var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(cullUsingExtents, true, appearance); + var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(cullUsingExtents, true, true, vs, appearance); var shadowVolumeAppearanceFS2D = shadowVolumeAppearanceShader2D.fragmentShaderSource; if (isPerInstanceColor) { parts = [shadowVolumeAppearanceFS2D]; @@ -749,8 +734,8 @@ define([ }); var vsColorSource2D = new ShaderSource({ - defines : isPerInstanceColor ? ['PER_INSTANCE_COLOR', 'COLUMBUS_VIEW_2D'] : ['COLUMBUS_VIEW_2D'], - sources : [vs] + defines : [extrudedDefine], + sources : [shadowVolumeAppearanceShader2D.vertexShaderSource] }); classificationPrimitive._spColor2D = ShaderProgram.replaceCache({ diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js index 529b81dbaba..f78371bf848 100644 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -4,6 +4,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Renderer/PixelDatatype', + '../Renderer/ShaderSource', // TODO: where should this file live actually? '../Scene/PerInstanceColorAppearance' ], function( Check, @@ -11,76 +12,79 @@ define([ defined, defineProperties, PixelDatatype, + ShaderSource, PerInstanceColorAppearance) { 'use strict'; - var shaderDependenciesScratch = new ShaderDependencies(); /** * Creates the shadow volume fragment shader for a ClassificationPrimitive to use a given appearance. * * @param {Boolean} extentsCulling Discard fragments outside the instance's spherical extents. * @param {Boolean} planarExtents + * @param {Boolean} columbusView2D * @param {Appearance} [appearance] An Appearance to be used with a ClassificationPrimitive. Leave undefined for picking. * @returns {String} Shader source for a fragment shader using the input appearance. * @private */ - function ShadowVolumeAppearanceShader(extentsCulling, planarExtents, appearance) { + function ShadowVolumeAppearanceShader(extentsCulling, planarExtents, columbusView2D, vertexShader, appearance) { //>>includeStart('debug', pragmas.debug); Check.typeOf.bool('extentsCulling', extentsCulling); Check.typeOf.bool('planarExtents', planarExtents); + Check.typeOf.bool('columbusView2D', columbusView2D); + Check.typeOf.string('vertexShader', vertexShader); //>>includeEnd('debug'); + var shaderDependencies = new ShaderDependencies(); + this._shaderDependencies = shaderDependencies; this._extentsCulling = extentsCulling; - this._planarExtents = planarExtents; - this._shaderSource = createShadowVolumeAppearanceShader(appearance, this._extentsCulling, this._planarExtents); - this._usesTextureCoordinates = shaderDependenciesScratch._requiresTextureCoordinates; + this._planarExtents = planarExtents || columbusView2D; + this._fragmenShaderSource = createShadowVolumeAppearanceFS(shaderDependencies, appearance, this._extentsCulling, this._planarExtents); + this._vertexShaderSource = createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, vertexShader); } defineProperties(ShadowVolumeAppearanceShader.prototype, { /** - * Whether or not the resulting shader uses texture coordinates. + * Whether or not the resulting shader's texture coordinates are computed from planar extents. * * @memberof ShadowVolumeAppearanceShader.prototype * @type {Boolean} * @readonly */ - usesTextureCoordinates : { + planarExtents : { get : function() { - return this._usesTextureCoordinates; + return this._planarExtents; } }, /** - * Whether or not the resulting shader's texture coordinates are computed from planar extents. - * + * The fragment shader source. * @memberof ShadowVolumeAppearanceShader.prototype - * @type {Boolean} + * @type {String} * @readonly */ - planarExtents : { + fragmentShaderSource : { get : function() { - return this._planarExtents; + return this._fragmenShaderSource; } }, - /** - * The fragment shader source. + /** + * The vertex shader source. * @memberof ShadowVolumeAppearanceShader.prototype * @type {String} * @readonly */ - fragmentShaderSource : { + vertexShaderSource : { get : function() { - return this._shaderSource; + return this._vertexShaderSource; } } }); - function createShadowVolumeAppearanceShader(appearance, extentsCull, planarExtents) { - var shaderDependencies = shaderDependenciesScratch.reset(); + function createShadowVolumeAppearanceFS(shaderDependencies, appearance, extentsCull, planarExtents) { if (!defined(appearance)) { - return getColorlessShader(extentsCull, planarExtents); + return getColorlessShaderFS(shaderDependencies, extentsCull, planarExtents); } if (appearance instanceof PerInstanceColorAppearance) { - return getPerInstanceColorShader(extentsCull, appearance.flat, planarExtents); + return getPerInstanceColorShaderFS(shaderDependencies, extentsCull, appearance.flat, planarExtents); } shaderDependencies.requiresTextureCoordinates = extentsCull; @@ -111,13 +115,13 @@ define([ 'varying vec4 v_stSineCosineUVScale;\n'; } - glsl += getLocalFunctions(shaderDependencies, planarExtents); + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCull, planarExtents); + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCull, planarExtents); glsl += ' czm_materialInput materialInput;\n'; if (usesNormalEC) { @@ -147,7 +151,7 @@ define([ return glsl; } - function getColorlessShader(extentsCulling, planarExtents) { + function getColorlessShaderFS(shaderDependencies, extentsCulling, planarExtents) { var glsl = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + @@ -160,18 +164,17 @@ define([ 'varying vec4 v_sphericalExtents;\n'; } - var shaderDependencies = shaderDependenciesScratch; shaderDependencies.requiresTextureCoordinates = extentsCulling; shaderDependencies.requiresNormalEC = false; - glsl += getLocalFunctions(shaderDependencies, planarExtents); + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); glsl += 'void main(void)\n' + '{\n'; glsl += ' bool culled = false;\n'; var outOfBoundsSnippet = ' culled = true;\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); glsl += ' if (!culled) {\n' + ' gl_FragColor.a = 1.0;\n' + // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource ' czm_writeDepthClampedToFarPlane();\n' + @@ -180,7 +183,7 @@ define([ return glsl; } - function getPerInstanceColorShader(extentsCulling, flatShading, planarExtents) { + function getPerInstanceColorShaderFS(shaderDependencies, extentsCulling, flatShading, planarExtents) { var glsl = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + @@ -194,16 +197,15 @@ define([ 'varying vec4 v_sphericalExtents;\n'; } - var shaderDependencies = shaderDependenciesScratch; shaderDependencies.requiresTextureCoordinates = extentsCulling; shaderDependencies.requiresNormalEC = !flatShading; - glsl += getLocalFunctions(shaderDependencies, planarExtents); + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); glsl += 'void main(void)\n' + '{\n'; - glsl += getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents); + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents); if (flatShading) { glsl += @@ -224,7 +226,7 @@ define([ return glsl; } - function getDependenciesAndCulling(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { + function getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { var glsl = ''; if (shaderDependencies.requiresEC) { glsl += @@ -271,7 +273,7 @@ define([ return glsl; } - function getLocalFunctions(shaderDependencies, planarExtents) { + function getLocalFunctionsFS(shaderDependencies, planarExtents) { var glsl = ''; if (shaderDependencies.requiresEC) { glsl += @@ -315,6 +317,80 @@ define([ return glsl; } + function createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, shadowVolumeVS) { + var glsl = ShaderSource.replaceMain(shadowVolumeVS, 'computePosition'); + + var isPerInstanceColor = defined(appearance) && appearance instanceof PerInstanceColorAppearance; + if (isPerInstanceColor) { + glsl += 'varying vec4 v_color;\n'; + } + + var spherical = !(planarExtents || columbusView2D); + if (shaderDependencies.requiresTextureCoordinates) { + if (spherical) { + glsl += + 'varying vec4 v_sphericalExtents;\n' + + 'varying vec4 v_stSineCosineUVScale;\n'; + } else { + glsl += + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' + + 'varying vec4 v_stSineCosineUVScale;\n'; + } + } + + glsl += + 'void main()\n' + + '{\n' + + ' computePosition();\n'; + if (isPerInstanceColor) { + glsl += 'v_color = czm_batchTable_color(batchId);\n'; + } + + // Add code for computing texture coordinate dependencies + if (shaderDependencies.requiresTextureCoordinates) { + if (spherical) { + glsl += + 'v_sphericalExtents = czm_batchTable_sphericalExtents(batchId);\n' + + 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; + } else { + // Two varieties of planar texcoords. 2D/CV case is "compressed" + if (columbusView2D) { + glsl += + 'vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId);\n' + + 'vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId);\n' + + 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz;\n' + + 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz;\n' + + '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;\n'; + } else { + glsl += + 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz;\n' + + 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz;\n' + + 'vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz;\n'; + } + glsl += + 'vec3 eastWard = southEastCorner - southWestCorner;\n' + + 'float eastExtent = length(eastWard);\n' + + 'eastWard /= eastExtent;\n' + + + 'vec3 northWard = northWestCorner - southWestCorner;\n' + + 'float northExtent = length(northWard);\n' + + 'northWard /= northExtent;\n' + + + 'v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner));\n' + + 'v_southPlane = vec4(northWard, -dot(northWard, southWestCorner));\n' + + 'v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent);\n' + + 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; + } + } + + glsl += + '}\n'; + + return glsl; + } + /** * Tracks shader dependencies. * @private @@ -326,14 +402,6 @@ define([ this._requiresTextureCoordinates = false; // depends on world coordinates, needed for material and for culling } - ShaderDependencies.prototype.reset = function() { - this._requiresEC = false; - this._requiresWC = false; - this._requiresNormalEC = false; - this._requiresTextureCoordinates = false; - return this; - }; - defineProperties(ShaderDependencies.prototype, { // Set when assessing final shading (flat vs. phong) and culling using computed texture coordinates requiresEC : { @@ -397,5 +465,3 @@ define([ return ShadowVolumeAppearanceShader; }); - -// TODO: have this inject code into ShadowVolumeVS so that's less messy diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index 777150ea67e..981c6f56fdd 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -4,8 +4,6 @@ #ifdef VECTOR_TILE uniform vec4 u_highlightColor; -//#else -//varying vec4 v_color; #endif void main(void) diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index eefefe2b3d4..33554d4f3f5 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -6,7 +6,6 @@ uniform mat4 u_modifiedModelViewProjection; #else attribute vec3 position3DHigh; attribute vec3 position3DLow; -//attribute vec4 color; attribute float batchId; #endif @@ -16,89 +15,11 @@ attribute vec3 extrudeDirection; uniform float u_globeMinimumAltitude; #endif -#ifndef VECTOR_TILE - -#ifdef SPHERICAL_EXTENTS -varying vec4 v_sphericalExtents; -varying vec4 v_stSineCosineUVScale; -#endif - -#ifdef PLANAR_EXTENTS -varying vec2 v_inversePlaneExtents; -varying vec4 v_westPlane; -varying vec4 v_southPlane; -varying vec4 v_stSineCosineUVScale; -#endif - -#ifdef COLUMBUS_VIEW_2D // ugh... okay this really needs to become more programmatic -varying vec2 v_inversePlaneExtents; -varying vec4 v_westPlane; -varying vec4 v_southPlane; -varying vec4 v_stSineCosineUVScale; -#endif - -#endif - -#ifdef PER_INSTANCE_COLOR -varying vec4 v_color; -#endif - void main() { #ifdef VECTOR_TILE gl_Position = czm_depthClampFarPlane(u_modifiedModelViewProjection * vec4(position, 1.0)); #else - -#ifdef PER_INSTANCE_COLOR - v_color = czm_batchTable_color(batchId); -#endif - -#ifdef SPHERICAL_EXTENTS - v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); - v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); -#endif - -#ifdef PLANAR_EXTENTS - vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz; - vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz; - vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz; - - vec3 eastWard = southEastCorner - southWestCorner; - float eastExtent = length(eastWard); - eastWard /= eastExtent; - - vec3 northWard = northWestCorner - southWestCorner; - float northExtent = length(northWard); - northWard /= northExtent; - - v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); - v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); - v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); - v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); -#endif - -#ifdef COLUMBUS_VIEW_2D - vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId); - vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId); - - vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz; - vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz; - 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; - - vec3 eastWard = southEastCorner - southWestCorner; - float eastExtent = length(eastWard); - eastWard /= eastExtent; - - vec3 northWard = northWestCorner - southWestCorner; - float northExtent = length(northWard); - northWard /= northExtent; - - v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); - v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); - v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); - v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); -#endif - vec4 position = czm_computePosition(); #ifdef EXTRUDED_GEOMETRY @@ -108,7 +29,6 @@ void main() //extrudeDirection is zero for the top layer position = position + vec4(extrudeDirection * delta, 0.0); #endif - gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position); #endif } From 2aba8f070550f973f59b5d7f59c811581119e84c Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 24 Apr 2018 13:29:10 -0400 Subject: [PATCH 21/40] fix log depth with GroundPrimitive materials [ci skip] --- Source/Renderer/AutomaticUniforms.js | 3288 +++++++++-------- Source/Scene/ShadowVolumeAppearanceShader.js | 29 +- .../Builtin/Functions/reverseLogDepth.glsl | 2 +- .../Builtin/Functions/vertexLogDepth.glsl | 2 +- .../Builtin/Functions/writeLogDepth.glsl | 4 +- Specs/Renderer/AutomaticUniformSpec.js | 26 +- 6 files changed, 1706 insertions(+), 1645 deletions(-) diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 1556c5b2ad9..4264d4e1d81 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1,1639 +1,1671 @@ define([ - '../Core/Cartesian3', - '../Core/Matrix4', - '../Core/WebGLConstants' - ], function( - Cartesian3, - Matrix4, - WebGLConstants) { - 'use strict'; - /*global WebGLRenderingContext*/ - - var viewerPositionWCScratch = new Cartesian3(); - - function AutomaticUniform(options) { - this._size = options.size; - this._datatype = options.datatype; - this.getValue = options.getValue; + '../Core/Cartesian3', + '../Core/Math', + '../Core/Matrix4', + '../Core/WebGLConstants' +], function( + Cartesian3, + CesiumMath, + Matrix4, + WebGLConstants) { +'use strict'; +/*global WebGLRenderingContext*/ + +var viewerPositionWCScratch = new Cartesian3(); + +function AutomaticUniform(options) { + this._size = options.size; + this._datatype = options.datatype; + this.getValue = options.getValue; +} + +// this check must use typeof, not defined, because defined doesn't work with undeclared variables. +if (typeof WebGLRenderingContext === 'undefined') { + return {}; +} + +var datatypeToGlsl = {}; +datatypeToGlsl[WebGLConstants.FLOAT] = 'float'; +datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2'; +datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3'; +datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4'; +datatypeToGlsl[WebGLConstants.INT] = 'int'; +datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2'; +datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3'; +datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4'; +datatypeToGlsl[WebGLConstants.BOOL] = 'bool'; +datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2'; +datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3'; +datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4'; +datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2'; +datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3'; +datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4'; +datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D'; +datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube'; + +AutomaticUniform.prototype.getDeclaration = function(name) { + var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name; + + var size = this._size; + if (size === 1) { + declaration += ';'; + } else { + declaration += '[' + size.toString() + '];'; } - // this check must use typeof, not defined, because defined doesn't work with undeclared variables. - if (typeof WebGLRenderingContext === 'undefined') { - return {}; - } + return declaration; +}; + +/** + * @private + */ +var AutomaticUniforms = { + /** + * An automatic GLSL uniform containing the viewport's x, y, width, + * and height properties in an vec4's x, y, z, + * and w components, respectively. + * + * @alias czm_viewport + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec4 czm_viewport; + * + * // Scale the window coordinate components to [0, 1] by dividing + * // by the viewport's width and height. + * vec2 v = gl_FragCoord.xy / czm_viewport.zw; + * + * @see Context#getViewport + */ + czm_viewport : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.viewportCartesian4; + } + }), - var datatypeToGlsl = {}; - datatypeToGlsl[WebGLConstants.FLOAT] = 'float'; - datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2'; - datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3'; - datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4'; - datatypeToGlsl[WebGLConstants.INT] = 'int'; - datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2'; - datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3'; - datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4'; - datatypeToGlsl[WebGLConstants.BOOL] = 'bool'; - datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2'; - datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3'; - datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4'; - datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2'; - datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3'; - datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4'; - datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D'; - datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube'; - - AutomaticUniform.prototype.getDeclaration = function(name) { - var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name; - - var size = this._size; - if (size === 1) { - declaration += ';'; - } else { - declaration += '[' + size.toString() + '];'; + /** + * An automatic GLSL uniform representing a 4x4 orthographic projection matrix that + * transforms window coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + *

+ * This transform is useful when a vertex shader inputs or manipulates window coordinates + * as done by {@link BillboardCollection}. + *

+ * Do not confuse {@link czm_viewportTransformation} with czm_viewportOrthographic. + * The former transforms from normalized device coordinates to window coordinates; the later transforms + * from window coordinates to clip coordinates, and is often used to assign to gl_Position. + * + * @alias czm_viewportOrthographic + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewportOrthographic; + * + * // Example + * gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0); + * + * @see UniformState#viewportOrthographic + * @see czm_viewport + * @see czm_viewportTransformation + * @see BillboardCollection + */ + czm_viewportOrthographic : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewportOrthographic; } + }), - return declaration; - }; + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms normalized device coordinates to window coordinates. The context's + * full viewport is used, and the depth range is assumed to be near = 0 + * and far = 1. + *

+ * This transform is useful when there is a need to manipulate window coordinates + * in a vertex shader as done by {@link BillboardCollection}. In many cases, + * this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates} + * will be used to transform directly from model to window coordinates. + *

+ * Do not confuse czm_viewportTransformation with {@link czm_viewportOrthographic}. + * The former transforms from normalized device coordinates to window coordinates; the later transforms + * from window coordinates to clip coordinates, and is often used to assign to gl_Position. + * + * @alias czm_viewportTransformation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewportTransformation; + * + * // Use czm_viewportTransformation as part of the + * // transform from model to window coordinates. + * vec4 q = czm_modelViewProjection * positionMC; // model to clip coordinates + * q.xyz /= q.w; // clip to normalized device coordinates (ndc) + * q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates + * + * @see UniformState#viewportTransformation + * @see czm_viewport + * @see czm_viewportOrthographic + * @see czm_modelToWindowCoordinates + * @see BillboardCollection + */ + czm_viewportTransformation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewportTransformation; + } + }), /** + * An automatic GLSL uniform representing the depth after + * only the globe has been rendered and packed into an RGBA texture. + * * @private + * + * @alias czm_globeDepthTexture + * @glslUniform + * + * @example + * // GLSL declaration + * uniform sampler2D czm_globeDepthTexture; + * + * // Get the depth at the current fragment + * vec2 coords = gl_FragCoord.xy / czm_viewport.zw; + * float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); + */ + czm_globeDepthTexture : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_2D, + getValue : function(uniformState) { + return uniformState.globeDepthTexture; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model transformation matrix that + * transforms model coordinates to world coordinates. + * + * @alias czm_model + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_model; + * + * // Example + * vec4 worldPosition = czm_model * modelPosition; + * + * @see UniformState#model + * @see czm_inverseModel + * @see czm_modelView + * @see czm_modelViewProjection + */ + czm_model : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.model; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model transformation matrix that + * transforms world coordinates to model coordinates. + * + * @alias czm_inverseModel + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModel; + * + * // Example + * vec4 modelPosition = czm_inverseModel * worldPosition; + * + * @see UniformState#inverseModel + * @see czm_model + * @see czm_inverseModelView + */ + czm_inverseModel : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModel; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view transformation matrix that + * transforms world coordinates to eye coordinates. + * + * @alias czm_view + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_view; + * + * // Example + * vec4 eyePosition = czm_view * worldPosition; + * + * @see UniformState#view + * @see czm_viewRotation + * @see czm_modelView + * @see czm_viewProjection + * @see czm_modelViewProjection + * @see czm_inverseView + */ + czm_view : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.view; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view transformation matrix that + * transforms 3D world coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_view}, but in 2D and Columbus View it represents the view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_view3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_view3D; + * + * // Example + * vec4 eyePosition3D = czm_view3D * worldPosition3D; + * + * @see UniformState#view3D + * @see czm_view + */ + czm_view3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.view3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 view rotation matrix that + * transforms vectors in world coordinates to eye coordinates. + * + * @alias czm_viewRotation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_viewRotation; + * + * // Example + * vec3 eyeVector = czm_viewRotation * worldVector; + * + * @see UniformState#viewRotation + * @see czm_view + * @see czm_inverseView + * @see czm_inverseViewRotation + */ + czm_viewRotation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.viewRotation; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 view rotation matrix that + * transforms vectors in 3D world coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_viewRotation3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_viewRotation3D; + * + * // Example + * vec3 eyeVector = czm_viewRotation3D * worldVector; + * + * @see UniformState#viewRotation3D + * @see czm_viewRotation + */ + czm_viewRotation3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.viewRotation3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to world coordinates. + * + * @alias czm_inverseView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseView; + * + * // Example + * vec4 worldPosition = czm_inverseView * eyePosition; + * + * @see UniformState#inverseView + * @see czm_view + * @see czm_inverseNormal + */ + czm_inverseView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from 3D eye coordinates to world coordinates. In 3D mode, this is identical to + * {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseView3D; + * + * // Example + * vec4 worldPosition = czm_inverseView3D * eyePosition; + * + * @see UniformState#inverseView3D + * @see czm_inverseView + */ + czm_inverseView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that + * transforms vectors from eye coordinates to world coordinates. + * + * @alias czm_inverseViewRotation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseViewRotation; + * + * // Example + * vec4 worldVector = czm_inverseViewRotation * eyeVector; + * + * @see UniformState#inverseView + * @see czm_view + * @see czm_viewRotation + * @see czm_inverseViewRotation + */ + czm_inverseViewRotation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseViewRotation; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that + * transforms vectors from 3D eye coordinates to world coordinates. In 3D mode, this is identical to + * {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseViewRotation3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseViewRotation3D; + * + * // Example + * vec4 worldVector = czm_inverseViewRotation3D * eyeVector; + * + * @see UniformState#inverseView3D + * @see czm_inverseViewRotation + */ + czm_inverseViewRotation3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseViewRotation3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 projection transformation matrix that + * transforms eye coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_projection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_projection; + * + * // Example + * gl_Position = czm_projection * eyePosition; + * + * @see UniformState#projection + * @see czm_viewProjection + * @see czm_modelViewProjection + * @see czm_infiniteProjection + */ + czm_projection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.projection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that + * transforms from clip coordinates to eye coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseProjection; + * + * // Example + * vec4 eyePosition = czm_inverseProjection * clipPosition; + * + * @see UniformState#inverseProjection + * @see czm_projection + */ + czm_inverseProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity, + * that transforms eye coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. An infinite far plane is used + * in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles + * are not clipped by the far plane. + * + * @alias czm_infiniteProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_infiniteProjection; + * + * // Example + * gl_Position = czm_infiniteProjection * eyePosition; + * + * @see UniformState#infiniteProjection + * @see czm_projection + * @see czm_modelViewInfiniteProjection + */ + czm_infiniteProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.infiniteProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms model coordinates to eye coordinates. + *

+ * Positions should be transformed to eye coordinates using czm_modelView and + * normals should be transformed using {@link czm_normal}. + * + * @alias czm_modelView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelView; + * + * // Example + * vec4 eyePosition = czm_modelView * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * vec4 eyePosition = czm_view * czm_model * modelPosition; + * + * @see UniformState#modelView + * @see czm_model + * @see czm_view + * @see czm_modelViewProjection + * @see czm_normal + */ + czm_modelView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms 3D model coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + *

+ * Positions should be transformed to eye coordinates using czm_modelView3D and + * normals should be transformed using {@link czm_normal3D}. + * + * @alias czm_modelView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelView3D; + * + * // Example + * vec4 eyePosition = czm_modelView3D * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * vec4 eyePosition = czm_view3D * czm_model * modelPosition; + * + * @see UniformState#modelView3D + * @see czm_modelView + */ + czm_modelView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms model coordinates, relative to the eye, to eye coordinates. This is used + * in conjunction with {@link czm_translateRelativeToEye}. + * + * @alias czm_modelViewRelativeToEye + * @glslUniform + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewRelativeToEye; + * + * // Example + * attribute vec3 positionHigh; + * attribute vec3 positionLow; + * + * void main() + * { + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_projection * (czm_modelViewRelativeToEye * p); + * } + * + * @see czm_modelViewProjectionRelativeToEye + * @see czm_translateRelativeToEye + * @see EncodedCartesian3 + */ + czm_modelViewRelativeToEye : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewRelativeToEye; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to model coordinates. + * + * @alias czm_inverseModelView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelView; + * + * // Example + * vec4 modelPosition = czm_inverseModelView * eyePosition; + * + * @see UniformState#inverseModelView + * @see czm_modelView + */ + czm_inverseModelView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to 3D model coordinates. In 3D mode, this is identical to + * {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseModelView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelView3D; + * + * // Example + * vec4 modelPosition = czm_inverseModelView3D * eyePosition; + * + * @see UniformState#inverseModelView + * @see czm_inverseModelView + * @see czm_modelView3D + */ + czm_inverseModelView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that + * transforms world coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_viewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewProjection; + * + * // Example + * vec4 gl_Position = czm_viewProjection * czm_model * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_projection * czm_view * czm_model * modelPosition; + * + * @see UniformState#viewProjection + * @see czm_view + * @see czm_projection + * @see czm_modelViewProjection + * @see czm_inverseViewProjection + */ + czm_viewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that + * transforms clip coordinates to world coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseViewProjection; + * + * // Example + * vec4 worldPosition = czm_inverseViewProjection * clipPosition; + * + * @see UniformState#inverseViewProjection + * @see czm_viewProjection + */ + czm_inverseViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_modelViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewProjection; + * + * // Example + * vec4 gl_Position = czm_modelViewProjection * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_projection * czm_view * czm_model * modelPosition; + * + * @see UniformState#modelViewProjection + * @see czm_model + * @see czm_view + * @see czm_projection + * @see czm_modelView + * @see czm_viewProjection + * @see czm_modelViewInfiniteProjection + * @see czm_inverseModelViewProjection + */ + czm_modelViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that + * transforms clip coordinates to model coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseModelViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelViewProjection; + * + * // Example + * vec4 modelPosition = czm_inverseModelViewProjection * clipPosition; + * + * @see UniformState#modelViewProjection + * @see czm_modelViewProjection + */ + czm_inverseModelViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates, relative to the eye, to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. This is used in + * conjunction with {@link czm_translateRelativeToEye}. + * + * @alias czm_modelViewProjectionRelativeToEye + * @glslUniform + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewProjectionRelativeToEye; + * + * // Example + * attribute vec3 positionHigh; + * attribute vec3 positionLow; + * + * void main() + * { + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_modelViewProjectionRelativeToEye * p; + * } + * + * @see czm_modelViewRelativeToEye + * @see czm_translateRelativeToEye + * @see EncodedCartesian3 + */ + czm_modelViewProjectionRelativeToEye : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewProjectionRelativeToEye; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. The projection matrix places + * the far plane at infinity. This is useful in algorithms like shadow volumes and GPU ray casting with + * proxy geometry to ensure that triangles are not clipped by the far plane. + * + * @alias czm_modelViewInfiniteProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewInfiniteProjection; + * + * // Example + * vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition; + * + * @see UniformState#modelViewInfiniteProjection + * @see czm_model + * @see czm_view + * @see czm_infiniteProjection + * @see czm_modelViewProjection + */ + czm_modelViewInfiniteProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewInfiniteProjection; + } + }), + + /** + * An automatic GLSL uniform that indicates if the current camera is orthographic in 3D. + * + * @alias czm_orthographicIn3D + * @see UniformState#orthographicIn3D + */ + czm_orthographicIn3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.orthographicIn3D ? 1 : 0; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in model coordinates to eye coordinates. + *

+ * Positions should be transformed to eye coordinates using {@link czm_modelView} and + * normals should be transformed using czm_normal. + * + * @alias czm_normal + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_normal; + * + * // Example + * vec3 eyeNormal = czm_normal * normal; + * + * @see UniformState#normal + * @see czm_inverseNormal + * @see czm_modelView + */ + czm_normal : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.normal; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in 3D model coordinates to eye coordinates. + * In 3D mode, this is identical to + * {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation + * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + *

+ * Positions should be transformed to eye coordinates using {@link czm_modelView3D} and + * normals should be transformed using czm_normal3D. + * + * @alias czm_normal3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_normal3D; + * + * // Example + * vec3 eyeNormal = czm_normal3D * normal; + * + * @see UniformState#normal3D + * @see czm_normal + */ + czm_normal3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.normal3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in eye coordinates to model coordinates. This is + * the opposite of the transform provided by {@link czm_normal}. + * + * @alias czm_inverseNormal + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseNormal; + * + * // Example + * vec3 normalMC = czm_inverseNormal * normalEC; + * + * @see UniformState#inverseNormal + * @see czm_normal + * @see czm_modelView + * @see czm_inverseView */ - var AutomaticUniforms = { - /** - * An automatic GLSL uniform containing the viewport's x, y, width, - * and height properties in an vec4's x, y, z, - * and w components, respectively. - * - * @alias czm_viewport - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec4 czm_viewport; - * - * // Scale the window coordinate components to [0, 1] by dividing - * // by the viewport's width and height. - * vec2 v = gl_FragCoord.xy / czm_viewport.zw; - * - * @see Context#getViewport - */ - czm_viewport : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.viewportCartesian4; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 orthographic projection matrix that - * transforms window coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - *

- * This transform is useful when a vertex shader inputs or manipulates window coordinates - * as done by {@link BillboardCollection}. - *

- * Do not confuse {@link czm_viewportTransformation} with czm_viewportOrthographic. - * The former transforms from normalized device coordinates to window coordinates; the later transforms - * from window coordinates to clip coordinates, and is often used to assign to gl_Position. - * - * @alias czm_viewportOrthographic - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewportOrthographic; - * - * // Example - * gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0); - * - * @see UniformState#viewportOrthographic - * @see czm_viewport - * @see czm_viewportTransformation - * @see BillboardCollection - */ - czm_viewportOrthographic : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewportOrthographic; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms normalized device coordinates to window coordinates. The context's - * full viewport is used, and the depth range is assumed to be near = 0 - * and far = 1. - *

- * This transform is useful when there is a need to manipulate window coordinates - * in a vertex shader as done by {@link BillboardCollection}. In many cases, - * this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates} - * will be used to transform directly from model to window coordinates. - *

- * Do not confuse czm_viewportTransformation with {@link czm_viewportOrthographic}. - * The former transforms from normalized device coordinates to window coordinates; the later transforms - * from window coordinates to clip coordinates, and is often used to assign to gl_Position. - * - * @alias czm_viewportTransformation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewportTransformation; - * - * // Use czm_viewportTransformation as part of the - * // transform from model to window coordinates. - * vec4 q = czm_modelViewProjection * positionMC; // model to clip coordinates - * q.xyz /= q.w; // clip to normalized device coordinates (ndc) - * q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates - * - * @see UniformState#viewportTransformation - * @see czm_viewport - * @see czm_viewportOrthographic - * @see czm_modelToWindowCoordinates - * @see BillboardCollection - */ - czm_viewportTransformation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewportTransformation; - } - }), - - /** - * An automatic GLSL uniform representing the depth after - * only the globe has been rendered and packed into an RGBA texture. - * - * @private - * - * @alias czm_globeDepthTexture - * @glslUniform - * - * @example - * // GLSL declaration - * uniform sampler2D czm_globeDepthTexture; - * - * // Get the depth at the current fragment - * vec2 coords = gl_FragCoord.xy / czm_viewport.zw; - * float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - */ - czm_globeDepthTexture : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_2D, - getValue : function(uniformState) { - return uniformState.globeDepthTexture; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model transformation matrix that - * transforms model coordinates to world coordinates. - * - * @alias czm_model - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_model; - * - * // Example - * vec4 worldPosition = czm_model * modelPosition; - * - * @see UniformState#model - * @see czm_inverseModel - * @see czm_modelView - * @see czm_modelViewProjection - */ - czm_model : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.model; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model transformation matrix that - * transforms world coordinates to model coordinates. - * - * @alias czm_inverseModel - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModel; - * - * // Example - * vec4 modelPosition = czm_inverseModel * worldPosition; - * - * @see UniformState#inverseModel - * @see czm_model - * @see czm_inverseModelView - */ - czm_inverseModel : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModel; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view transformation matrix that - * transforms world coordinates to eye coordinates. - * - * @alias czm_view - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_view; - * - * // Example - * vec4 eyePosition = czm_view * worldPosition; - * - * @see UniformState#view - * @see czm_viewRotation - * @see czm_modelView - * @see czm_viewProjection - * @see czm_modelViewProjection - * @see czm_inverseView - */ - czm_view : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.view; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view transformation matrix that - * transforms 3D world coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_view}, but in 2D and Columbus View it represents the view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_view3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_view3D; - * - * // Example - * vec4 eyePosition3D = czm_view3D * worldPosition3D; - * - * @see UniformState#view3D - * @see czm_view - */ - czm_view3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.view3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 view rotation matrix that - * transforms vectors in world coordinates to eye coordinates. - * - * @alias czm_viewRotation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_viewRotation; - * - * // Example - * vec3 eyeVector = czm_viewRotation * worldVector; - * - * @see UniformState#viewRotation - * @see czm_view - * @see czm_inverseView - * @see czm_inverseViewRotation - */ - czm_viewRotation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.viewRotation; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 view rotation matrix that - * transforms vectors in 3D world coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_viewRotation3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_viewRotation3D; - * - * // Example - * vec3 eyeVector = czm_viewRotation3D * worldVector; - * - * @see UniformState#viewRotation3D - * @see czm_viewRotation - */ - czm_viewRotation3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.viewRotation3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to world coordinates. - * - * @alias czm_inverseView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseView; - * - * // Example - * vec4 worldPosition = czm_inverseView * eyePosition; - * - * @see UniformState#inverseView - * @see czm_view - * @see czm_inverseNormal - */ - czm_inverseView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from 3D eye coordinates to world coordinates. In 3D mode, this is identical to - * {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseView3D; - * - * // Example - * vec4 worldPosition = czm_inverseView3D * eyePosition; - * - * @see UniformState#inverseView3D - * @see czm_inverseView - */ - czm_inverseView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that - * transforms vectors from eye coordinates to world coordinates. - * - * @alias czm_inverseViewRotation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseViewRotation; - * - * // Example - * vec4 worldVector = czm_inverseViewRotation * eyeVector; - * - * @see UniformState#inverseView - * @see czm_view - * @see czm_viewRotation - * @see czm_inverseViewRotation - */ - czm_inverseViewRotation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseViewRotation; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that - * transforms vectors from 3D eye coordinates to world coordinates. In 3D mode, this is identical to - * {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseViewRotation3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseViewRotation3D; - * - * // Example - * vec4 worldVector = czm_inverseViewRotation3D * eyeVector; - * - * @see UniformState#inverseView3D - * @see czm_inverseViewRotation - */ - czm_inverseViewRotation3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseViewRotation3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 projection transformation matrix that - * transforms eye coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_projection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_projection; - * - * // Example - * gl_Position = czm_projection * eyePosition; - * - * @see UniformState#projection - * @see czm_viewProjection - * @see czm_modelViewProjection - * @see czm_infiniteProjection - */ - czm_projection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.projection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that - * transforms from clip coordinates to eye coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseProjection; - * - * // Example - * vec4 eyePosition = czm_inverseProjection * clipPosition; - * - * @see UniformState#inverseProjection - * @see czm_projection - */ - czm_inverseProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity, - * that transforms eye coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. An infinite far plane is used - * in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles - * are not clipped by the far plane. - * - * @alias czm_infiniteProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_infiniteProjection; - * - * // Example - * gl_Position = czm_infiniteProjection * eyePosition; - * - * @see UniformState#infiniteProjection - * @see czm_projection - * @see czm_modelViewInfiniteProjection - */ - czm_infiniteProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.infiniteProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms model coordinates to eye coordinates. - *

- * Positions should be transformed to eye coordinates using czm_modelView and - * normals should be transformed using {@link czm_normal}. - * - * @alias czm_modelView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelView; - * - * // Example - * vec4 eyePosition = czm_modelView * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * vec4 eyePosition = czm_view * czm_model * modelPosition; - * - * @see UniformState#modelView - * @see czm_model - * @see czm_view - * @see czm_modelViewProjection - * @see czm_normal - */ - czm_modelView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms 3D model coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - *

- * Positions should be transformed to eye coordinates using czm_modelView3D and - * normals should be transformed using {@link czm_normal3D}. - * - * @alias czm_modelView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelView3D; - * - * // Example - * vec4 eyePosition = czm_modelView3D * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * vec4 eyePosition = czm_view3D * czm_model * modelPosition; - * - * @see UniformState#modelView3D - * @see czm_modelView - */ - czm_modelView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms model coordinates, relative to the eye, to eye coordinates. This is used - * in conjunction with {@link czm_translateRelativeToEye}. - * - * @alias czm_modelViewRelativeToEye - * @glslUniform - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewRelativeToEye; - * - * // Example - * attribute vec3 positionHigh; - * attribute vec3 positionLow; - * - * void main() - * { - * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_projection * (czm_modelViewRelativeToEye * p); - * } - * - * @see czm_modelViewProjectionRelativeToEye - * @see czm_translateRelativeToEye - * @see EncodedCartesian3 - */ - czm_modelViewRelativeToEye : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewRelativeToEye; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to model coordinates. - * - * @alias czm_inverseModelView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelView; - * - * // Example - * vec4 modelPosition = czm_inverseModelView * eyePosition; - * - * @see UniformState#inverseModelView - * @see czm_modelView - */ - czm_inverseModelView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to 3D model coordinates. In 3D mode, this is identical to - * {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseModelView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelView3D; - * - * // Example - * vec4 modelPosition = czm_inverseModelView3D * eyePosition; - * - * @see UniformState#inverseModelView - * @see czm_inverseModelView - * @see czm_modelView3D - */ - czm_inverseModelView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that - * transforms world coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_viewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewProjection; - * - * // Example - * vec4 gl_Position = czm_viewProjection * czm_model * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_projection * czm_view * czm_model * modelPosition; - * - * @see UniformState#viewProjection - * @see czm_view - * @see czm_projection - * @see czm_modelViewProjection - * @see czm_inverseViewProjection - */ - czm_viewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that - * transforms clip coordinates to world coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseViewProjection; - * - * // Example - * vec4 worldPosition = czm_inverseViewProjection * clipPosition; - * - * @see UniformState#inverseViewProjection - * @see czm_viewProjection - */ - czm_inverseViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_modelViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewProjection; - * - * // Example - * vec4 gl_Position = czm_modelViewProjection * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_projection * czm_view * czm_model * modelPosition; - * - * @see UniformState#modelViewProjection - * @see czm_model - * @see czm_view - * @see czm_projection - * @see czm_modelView - * @see czm_viewProjection - * @see czm_modelViewInfiniteProjection - * @see czm_inverseModelViewProjection - */ - czm_modelViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that - * transforms clip coordinates to model coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseModelViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelViewProjection; - * - * // Example - * vec4 modelPosition = czm_inverseModelViewProjection * clipPosition; - * - * @see UniformState#modelViewProjection - * @see czm_modelViewProjection - */ - czm_inverseModelViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates, relative to the eye, to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. This is used in - * conjunction with {@link czm_translateRelativeToEye}. - * - * @alias czm_modelViewProjectionRelativeToEye - * @glslUniform - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewProjectionRelativeToEye; - * - * // Example - * attribute vec3 positionHigh; - * attribute vec3 positionLow; - * - * void main() - * { - * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_modelViewProjectionRelativeToEye * p; - * } - * - * @see czm_modelViewRelativeToEye - * @see czm_translateRelativeToEye - * @see EncodedCartesian3 - */ - czm_modelViewProjectionRelativeToEye : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewProjectionRelativeToEye; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. The projection matrix places - * the far plane at infinity. This is useful in algorithms like shadow volumes and GPU ray casting with - * proxy geometry to ensure that triangles are not clipped by the far plane. - * - * @alias czm_modelViewInfiniteProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewInfiniteProjection; - * - * // Example - * vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition; - * - * @see UniformState#modelViewInfiniteProjection - * @see czm_model - * @see czm_view - * @see czm_infiniteProjection - * @see czm_modelViewProjection - */ - czm_modelViewInfiniteProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewInfiniteProjection; - } - }), - - /** - * An automatic GLSL uniform that indicates if the current camera is orthographic in 3D. - * - * @alias czm_orthographicIn3D - * @see UniformState#orthographicIn3D - */ - czm_orthographicIn3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.orthographicIn3D ? 1 : 0; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in model coordinates to eye coordinates. - *

- * Positions should be transformed to eye coordinates using {@link czm_modelView} and - * normals should be transformed using czm_normal. - * - * @alias czm_normal - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_normal; - * - * // Example - * vec3 eyeNormal = czm_normal * normal; - * - * @see UniformState#normal - * @see czm_inverseNormal - * @see czm_modelView - */ - czm_normal : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.normal; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in 3D model coordinates to eye coordinates. - * In 3D mode, this is identical to - * {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation - * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - *

- * Positions should be transformed to eye coordinates using {@link czm_modelView3D} and - * normals should be transformed using czm_normal3D. - * - * @alias czm_normal3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_normal3D; - * - * // Example - * vec3 eyeNormal = czm_normal3D * normal; - * - * @see UniformState#normal3D - * @see czm_normal - */ - czm_normal3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.normal3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in eye coordinates to model coordinates. This is - * the opposite of the transform provided by {@link czm_normal}. - * - * @alias czm_inverseNormal - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseNormal; - * - * // Example - * vec3 normalMC = czm_inverseNormal * normalEC; - * - * @see UniformState#inverseNormal - * @see czm_normal - * @see czm_modelView - * @see czm_inverseView - */ - czm_inverseNormal : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseNormal; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in eye coordinates to 3D model coordinates. This is - * the opposite of the transform provided by {@link czm_normal}. - * In 3D mode, this is identical to - * {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation - * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseNormal3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseNormal3D; - * - * // Example - * vec3 normalMC = czm_inverseNormal3D * normalEC; - * - * @see UniformState#inverseNormal3D - * @see czm_inverseNormal - */ - czm_inverseNormal3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseNormal3D; - } - }), - - /** - * An automatic GLSL uniform containing height (x) and height squared (y) - * of the eye (camera) in the 2D scene in meters. - * - * @alias czm_eyeHeight2D - * @glslUniform - * - * @see UniformState#eyeHeight2D - */ - czm_eyeHeight2D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.eyeHeight2D; - } - }), - - /** - * An automatic GLSL uniform containing the near distance (x) and the far distance (y) - * of the frustum defined by the camera. This is the largest possible frustum, not an individual - * frustum used for multi-frustum rendering. - * - * @alias czm_entireFrustum - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec2 czm_entireFrustum; - * - * // Example - * float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x; - * - * @see UniformState#entireFrustum - * @see czm_currentFrustum - */ - czm_entireFrustum : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.entireFrustum; - } - }), - - /** - * An automatic GLSL uniform containing the near distance (x) and the far distance (y) - * of the frustum defined by the camera. This is the individual - * frustum used for multi-frustum rendering. - * - * @alias czm_currentFrustum - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec2 czm_currentFrustum; - * - * // Example - * float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x; - * - * @see UniformState#currentFrustum - * @see czm_entireFrustum - */ - czm_currentFrustum : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.currentFrustum; - } - }), - - /** - * The distances to the frustum planes. The top, bottom, left and right distances are - * the x, y, z, and w components, respectively. - * - * @alias czm_frustumPlanes - * @glslUniform - */ - czm_frustumPlanes : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.frustumPlanes; - } - }), - - /** - * The log of the current frustums far plane. Used for computing the log depth. - * - * @alias czm_logFarDistance - * @glslUniform - * - * @private - */ - czm_logFarDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.logFarDistance; - } - }), - - /** - * An automatic GLSL uniform representing the sun position in world coordinates. - * - * @alias czm_sunPositionWC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunPositionWC; - * - * @see UniformState#sunPositionWC - * @see czm_sunPositionColumbusView - * @see czm_sunDirectionWC - */ - czm_sunPositionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunPositionWC; - } - }), - - /** - * An automatic GLSL uniform representing the sun position in Columbus view world coordinates. - * - * @alias czm_sunPositionColumbusView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunPositionColumbusView; - * - * @see UniformState#sunPositionColumbusView - * @see czm_sunPositionWC - */ - czm_sunPositionColumbusView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunPositionColumbusView; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_sunDirectionEC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunDirectionEC; - * - * // Example - * float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0); - * - * @see UniformState#sunDirectionEC - * @see czm_moonDirectionEC - * @see czm_sunDirectionWC - */ - czm_sunDirectionEC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunDirectionEC; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the sun in world coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_sunDirectionWC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunDirectionWC; - * - * @see UniformState#sunDirectionWC - * @see czm_sunPositionWC - * @see czm_sunDirectionEC - */ - czm_sunDirectionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunDirectionWC; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_moonDirectionEC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_moonDirectionEC; - * - * // Example - * float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0); - * - * @see UniformState#moonDirectionEC - * @see czm_sunDirectionEC - */ - czm_moonDirectionEC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.moonDirectionEC; - } - }), - - /** - * An automatic GLSL uniform representing the high bits of the camera position in model - * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering - * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. - * - * @alias czm_encodedCameraPositionMCHigh - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_encodedCameraPositionMCHigh; - * - * @see czm_encodedCameraPositionMCLow - * @see czm_modelViewRelativeToEye - * @see czm_modelViewProjectionRelativeToEye - */ - czm_encodedCameraPositionMCHigh : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.encodedCameraPositionMCHigh; - } - }), - - /** - * An automatic GLSL uniform representing the low bits of the camera position in model - * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering - * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. - * - * @alias czm_encodedCameraPositionMCLow - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_encodedCameraPositionMCLow; - * - * @see czm_encodedCameraPositionMCHigh - * @see czm_modelViewRelativeToEye - * @see czm_modelViewProjectionRelativeToEye - */ - czm_encodedCameraPositionMCLow : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.encodedCameraPositionMCLow; - } - }), - - /** - * An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates. - * - * @alias czm_viewerPositionWC - * @glslUniform - * - * @example - * // GLSL declaration - * uniform vec3 czm_viewerPositionWC; - */ - czm_viewerPositionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch); - } - }), - - /** - * An automatic GLSL uniform representing the frame number. This uniform is automatically incremented - * every frame. - * - * @alias czm_frameNumber - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_frameNumber; - */ - czm_frameNumber : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.frameNumber; - } - }), - - /** - * An automatic GLSL uniform representing the current morph transition time between - * 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D. - * - * @alias czm_morphTime - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_morphTime; - * - * // Example - * vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime); - */ - czm_morphTime : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.morphTime; - } - }), - - /** - * An automatic GLSL uniform representing the current {@link SceneMode}, expressed - * as a float. - * - * @alias czm_sceneMode - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform float czm_sceneMode; - * - * // Example - * if (czm_sceneMode == czm_sceneMode2D) - * { - * eyeHeightSq = czm_eyeHeight2D.y; - * } - * - * @see czm_sceneMode2D - * @see czm_sceneModeColumbusView - * @see czm_sceneMode3D - * @see czm_sceneModeMorphing - */ - czm_sceneMode : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.mode; - } - }), - - /** - * An automatic GLSL uniform representing the current rendering pass. - * - * @alias czm_pass - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_pass; - * - * // Example - * if ((czm_pass == czm_passTranslucent) && isOpaque()) - * { - * gl_Position *= 0.0; // Cull opaque geometry in the translucent pass - * } - */ - czm_pass : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.pass; - } - }), - - /** - * An automatic GLSL uniform representing the current scene background color. - * - * @alias czm_backgroundColor - * @glslUniform - * - * @example - * // GLSL declaration - * uniform vec4 czm_backgroundColor; - * - * // Example: If the given color's RGB matches the background color, invert it. - * vec4 adjustColorForContrast(vec4 color) - * { - * if (czm_backgroundColor.rgb == color.rgb) - * { - * color.rgb = vec3(1.0) - color.rgb; - * } - * - * return color; - * } - */ - czm_backgroundColor : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.backgroundColor; - } - }), - - /** - * An automatic GLSL uniform containing the BRDF look up texture used for image-based lighting computations. - * - * @alias czm_brdfLut - * @glslUniform - * - * @example - * // GLSL declaration - * uniform sampler2D czm_brdfLut; - * - * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels - * float roughness = 0.5; - * float NdotV = dot(normal, view); - * vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; - */ - czm_brdfLut : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_2D, - getValue : function(uniformState) { - return uniformState.brdfLut; - } - }), - - /** - * An automatic GLSL uniform containing the environment map used within the scene. - * - * @alias czm_environmentMap - * @glslUniform - * - * @example - * // GLSL declaration - * uniform samplerCube czm_environmentMap; - * - * // Example: Create a perfect reflection of the environment map on a model - * float reflected = reflect(view, normal); - * vec4 reflectedColor = textureCube(czm_environmentMap, reflected); - */ - czm_environmentMap : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_CUBE, - getValue : function(uniformState) { - return uniformState.environmentMap; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that transforms - * from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time. - * - * @alias czm_temeToPseudoFixed - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_temeToPseudoFixed; - * - * // Example - * vec3 pseudoFixed = czm_temeToPseudoFixed * teme; - * - * @see UniformState#temeToPseudoFixedMatrix - * @see Transforms.computeTemeToPseudoFixedMatrix - */ - czm_temeToPseudoFixed : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.temeToPseudoFixedMatrix; - } - }), - - /** - * An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space. - * - * @alias czm_resolutionScale - * @glslUniform - * - * @example - * uniform float czm_resolutionScale; - */ - czm_resolutionScale : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.resolutionScale; - } - }), - - /** - * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera. - * - * @alias czm_fogDensity - * @glslUniform - * - * @see czm_fog - */ - czm_fogDensity : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.fogDensity; - } - }), - - /** - * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. - * This will be in pixel coordinates relative to the canvas. - * - * @alias czm_imagerySplitPosition - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform float czm_imagerySplitPosition; - */ - czm_imagerySplitPosition : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.imagerySplitPosition; - } - }), - - /** - * An automatic GLSL uniform scalar representing the geometric tolerance per meter - * - * @alias czm_geometricToleranceOverMeter - * @glslUniform - */ - czm_geometricToleranceOverMeter : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.geometricToleranceOverMeter; - } - }), - - /** - * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points - * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, - * the depth test should never be applied. - * - * @alias czm_minimumDisableDepthTestDistance - * @glslUniform - */ - czm_minimumDisableDepthTestDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.minimumDisableDepthTestDistance; - } - }), - - /** - * An automatic GLSL uniform that will be the highlight color of unclassified 3D Tiles. - * - * @alias czm_invertClassificationColor - * @glslUniform - */ - czm_invertClassificationColor : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.invertClassificationColor; - } - }) - }; - - return AutomaticUniforms; + czm_inverseNormal : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseNormal; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in eye coordinates to 3D model coordinates. This is + * the opposite of the transform provided by {@link czm_normal}. + * In 3D mode, this is identical to + * {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation + * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseNormal3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseNormal3D; + * + * // Example + * vec3 normalMC = czm_inverseNormal3D * normalEC; + * + * @see UniformState#inverseNormal3D + * @see czm_inverseNormal + */ + czm_inverseNormal3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseNormal3D; + } + }), + + /** + * An automatic GLSL uniform containing height (x) and height squared (y) + * of the eye (camera) in the 2D scene in meters. + * + * @alias czm_eyeHeight2D + * @glslUniform + * + * @see UniformState#eyeHeight2D + */ + czm_eyeHeight2D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.eyeHeight2D; + } + }), + + /** + * An automatic GLSL uniform containing the near distance (x) and the far distance (y) + * of the frustum defined by the camera. This is the largest possible frustum, not an individual + * frustum used for multi-frustum rendering. + * + * @alias czm_entireFrustum + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec2 czm_entireFrustum; + * + * // Example + * float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x; + * + * @see UniformState#entireFrustum + * @see czm_currentFrustum + */ + czm_entireFrustum : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.entireFrustum; + } + }), + + /** + * An automatic GLSL uniform containing the near distance (x) and the far distance (y) + * of the frustum defined by the camera. This is the individual + * frustum used for multi-frustum rendering. + * + * @alias czm_currentFrustum + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec2 czm_currentFrustum; + * + * // Example + * float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x; + * + * @see UniformState#currentFrustum + * @see czm_entireFrustum + */ + czm_currentFrustum : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.currentFrustum; + } + }), + + /** + * The distances to the frustum planes. The top, bottom, left and right distances are + * the x, y, z, and w components, respectively. + * + * @alias czm_frustumPlanes + * @glslUniform + */ + czm_frustumPlanes : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.frustumPlanes; + } + }), + + /** + * The log2 of the current frustums far plane. Used for computing the log depth. + * + * @alias czm_log2FarDistance + * @glslUniform + * + * @private + */ + czm_log2FarDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.logFarDistance; + } + }), + + /** + * An automatic GLSL uniform containing log2 of the far distance + 1.0. + * This is used when reversing log depth computations. + * + * @alias czm_log2FarPlusOne + * @glslUniform + */ + czm_log2FarPlusOne : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return CesiumMath.log2(uniformState.currentFrustum.y + 1.0); + } + }), + + /** + * An automatic GLSL uniform containing log2 of the near distance. + * This is used when writing log depth in the fragment shader. + * + * @alias czm_log2NearDistance + * @glslUniform + */ + czm_log2NearDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return CesiumMath.log2(uniformState.currentFrustum.x); + } + }), + + /** + * An automatic GLSL uniform representing the sun position in world coordinates. + * + * @alias czm_sunPositionWC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunPositionWC; + * + * @see UniformState#sunPositionWC + * @see czm_sunPositionColumbusView + * @see czm_sunDirectionWC + */ + czm_sunPositionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunPositionWC; + } + }), + + /** + * An automatic GLSL uniform representing the sun position in Columbus view world coordinates. + * + * @alias czm_sunPositionColumbusView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunPositionColumbusView; + * + * @see UniformState#sunPositionColumbusView + * @see czm_sunPositionWC + */ + czm_sunPositionColumbusView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunPositionColumbusView; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_sunDirectionEC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunDirectionEC; + * + * // Example + * float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0); + * + * @see UniformState#sunDirectionEC + * @see czm_moonDirectionEC + * @see czm_sunDirectionWC + */ + czm_sunDirectionEC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunDirectionEC; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the sun in world coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_sunDirectionWC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunDirectionWC; + * + * @see UniformState#sunDirectionWC + * @see czm_sunPositionWC + * @see czm_sunDirectionEC + */ + czm_sunDirectionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunDirectionWC; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_moonDirectionEC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_moonDirectionEC; + * + * // Example + * float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0); + * + * @see UniformState#moonDirectionEC + * @see czm_sunDirectionEC + */ + czm_moonDirectionEC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.moonDirectionEC; + } + }), + + /** + * An automatic GLSL uniform representing the high bits of the camera position in model + * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering + * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. + * + * @alias czm_encodedCameraPositionMCHigh + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_encodedCameraPositionMCHigh; + * + * @see czm_encodedCameraPositionMCLow + * @see czm_modelViewRelativeToEye + * @see czm_modelViewProjectionRelativeToEye + */ + czm_encodedCameraPositionMCHigh : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.encodedCameraPositionMCHigh; + } + }), + + /** + * An automatic GLSL uniform representing the low bits of the camera position in model + * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering + * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. + * + * @alias czm_encodedCameraPositionMCLow + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_encodedCameraPositionMCLow; + * + * @see czm_encodedCameraPositionMCHigh + * @see czm_modelViewRelativeToEye + * @see czm_modelViewProjectionRelativeToEye + */ + czm_encodedCameraPositionMCLow : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.encodedCameraPositionMCLow; + } + }), + + /** + * An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates. + * + * @alias czm_viewerPositionWC + * @glslUniform + * + * @example + * // GLSL declaration + * uniform vec3 czm_viewerPositionWC; + */ + czm_viewerPositionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch); + } + }), + + /** + * An automatic GLSL uniform representing the frame number. This uniform is automatically incremented + * every frame. + * + * @alias czm_frameNumber + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_frameNumber; + */ + czm_frameNumber : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.frameNumber; + } + }), + + /** + * An automatic GLSL uniform representing the current morph transition time between + * 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D. + * + * @alias czm_morphTime + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_morphTime; + * + * // Example + * vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime); + */ + czm_morphTime : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.morphTime; + } + }), + + /** + * An automatic GLSL uniform representing the current {@link SceneMode}, expressed + * as a float. + * + * @alias czm_sceneMode + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform float czm_sceneMode; + * + * // Example + * if (czm_sceneMode == czm_sceneMode2D) + * { + * eyeHeightSq = czm_eyeHeight2D.y; + * } + * + * @see czm_sceneMode2D + * @see czm_sceneModeColumbusView + * @see czm_sceneMode3D + * @see czm_sceneModeMorphing + */ + czm_sceneMode : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.mode; + } + }), + + /** + * An automatic GLSL uniform representing the current rendering pass. + * + * @alias czm_pass + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_pass; + * + * // Example + * if ((czm_pass == czm_passTranslucent) && isOpaque()) + * { + * gl_Position *= 0.0; // Cull opaque geometry in the translucent pass + * } + */ + czm_pass : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.pass; + } + }), + + /** + * An automatic GLSL uniform representing the current scene background color. + * + * @alias czm_backgroundColor + * @glslUniform + * + * @example + * // GLSL declaration + * uniform vec4 czm_backgroundColor; + * + * // Example: If the given color's RGB matches the background color, invert it. + * vec4 adjustColorForContrast(vec4 color) + * { + * if (czm_backgroundColor.rgb == color.rgb) + * { + * color.rgb = vec3(1.0) - color.rgb; + * } + * + * return color; + * } + */ + czm_backgroundColor : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.backgroundColor; + } + }), + + /** + * An automatic GLSL uniform containing the BRDF look up texture used for image-based lighting computations. + * + * @alias czm_brdfLut + * @glslUniform + * + * @example + * // GLSL declaration + * uniform sampler2D czm_brdfLut; + * + * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels + * float roughness = 0.5; + * float NdotV = dot(normal, view); + * vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; + */ + czm_brdfLut : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_2D, + getValue : function(uniformState) { + return uniformState.brdfLut; + } + }), + + /** + * An automatic GLSL uniform containing the environment map used within the scene. + * + * @alias czm_environmentMap + * @glslUniform + * + * @example + * // GLSL declaration + * uniform samplerCube czm_environmentMap; + * + * // Example: Create a perfect reflection of the environment map on a model + * float reflected = reflect(view, normal); + * vec4 reflectedColor = textureCube(czm_environmentMap, reflected); + */ + czm_environmentMap : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_CUBE, + getValue : function(uniformState) { + return uniformState.environmentMap; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that transforms + * from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time. + * + * @alias czm_temeToPseudoFixed + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_temeToPseudoFixed; + * + * // Example + * vec3 pseudoFixed = czm_temeToPseudoFixed * teme; + * + * @see UniformState#temeToPseudoFixedMatrix + * @see Transforms.computeTemeToPseudoFixedMatrix + */ + czm_temeToPseudoFixed : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.temeToPseudoFixedMatrix; + } + }), + + /** + * An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space. + * + * @alias czm_resolutionScale + * @glslUniform + * + * @example + * uniform float czm_resolutionScale; + */ + czm_resolutionScale : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.resolutionScale; + } + }), + + /** + * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera. + * + * @alias czm_fogDensity + * @glslUniform + * + * @see czm_fog + */ + czm_fogDensity : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.fogDensity; + } + }), + + /** + * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. + * This will be in pixel coordinates relative to the canvas. + * + * @alias czm_imagerySplitPosition + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform float czm_imagerySplitPosition; + */ + czm_imagerySplitPosition : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.imagerySplitPosition; + } + }), + + /** + * An automatic GLSL uniform scalar representing the geometric tolerance per meter + * + * @alias czm_geometricToleranceOverMeter + * @glslUniform + */ + czm_geometricToleranceOverMeter : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.geometricToleranceOverMeter; + } + }), + + /** + * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points + * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, + * the depth test should never be applied. + * + * @alias czm_minimumDisableDepthTestDistance + * @glslUniform + */ + czm_minimumDisableDepthTestDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.minimumDisableDepthTestDistance; + } + }), + + /** + * An automatic GLSL uniform that will be the highlight color of unclassified 3D Tiles. + * + * @alias czm_invertClassificationColor + * @glslUniform + */ + czm_invertClassificationColor : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.invertClassificationColor; + } + }) +}; + +return AutomaticUniforms; }); diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js index 94bbd19b9e9..4812047c9a7 100644 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ b/Source/Scene/ShadowVolumeAppearanceShader.js @@ -279,31 +279,38 @@ define([ glsl += 'vec4 getEyeCoordinate(vec2 fragCoord) {\n' + ' vec2 coords = fragCoord / czm_viewport.zw;\n' + - ' float depth = czm_reverseLogDepth(czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)));\n' + - ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + + ' float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + + ' vec4 windowCoord = vec4(fragCoord, czm_reverseLogDepth(logDepthOrDepth), 1.0);\n' + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + '#ifdef LOG_DEPTH\n' + + // Essentially same as reverseLogDepth but without normalization. Better precision when using log depth. + ' eyeCoordinate.w = 1.0 / (pow(2.0, logDepthOrDepth * czm_log2FarPlusOne) - 1.0);\n' + + '#endif\n' + ' return eyeCoordinate;\n' + '}\n'; } if (shaderDependencies.requiresNormalEC) { glsl += - 'vec3 getEyeCoordinate3FromWindowCoordordinate(vec2 fragCoord, float depth) {\n' + - ' vec4 windowCoord = vec4(fragCoord, depth, 1.0);\n' + + 'vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) {\n' + + ' vec4 windowCoord = vec4(fragCoord, czm_reverseLogDepth(logDepthOrDepth), 1.0);\n' + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + '#ifdef LOG_DEPTH\n' + + // Essentially same as reverseLogDepth but without normalization. Better precision when using log depth. + ' eyeCoordinate.w = 1.0 / (pow(2.0, logDepthOrDepth * czm_log2FarPlusOne) - 1.0);\n' + + '#endif\n' + ' return eyeCoordinate.xyz / eyeCoordinate.w;\n' + '}\n' + 'vec3 getVectorFromOffset(vec4 eyeCoordinate, vec2 glFragCoordXY, vec2 positiveOffset) {\n' + ' // Sample depths at both offset and negative offset\n' + - ' float upOrRightDepth = czm_reverseLogDepth(czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw)));\n' + - ' float downOrLeftDepth = czm_reverseLogDepth(czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw)));\n' + - ' // Explicitly evaluate both paths\n' + + ' float upOrRightLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw));\n' + + ' float downOrLeftLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw));\n' + + ' // Explicitly evaluate both paths\n' + // Necessary for multifrustum and for GroundPrimitives at the edges of the screen ' bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw);\n' + - ' float useUpOrRight = float(upOrRightDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + + ' float useUpOrRight = float(upOrRightLogDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + - - ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordordinate(glFragCoordXY + positiveOffset, upOrRightDepth);\n' + - ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordordinate(glFragCoordXY - positiveOffset, downOrLeftDepth);\n' + + ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY + positiveOffset, upOrRightLogDepth);\n' + + ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY - positiveOffset, downOrLeftLogDepth);\n' + ' return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft;\n' + '}\n'; diff --git a/Source/Shaders/Builtin/Functions/reverseLogDepth.glsl b/Source/Shaders/Builtin/Functions/reverseLogDepth.glsl index e8719f24a9b..c6033d6d878 100644 --- a/Source/Shaders/Builtin/Functions/reverseLogDepth.glsl +++ b/Source/Shaders/Builtin/Functions/reverseLogDepth.glsl @@ -3,7 +3,7 @@ float czm_reverseLogDepth(float logZ) #ifdef LOG_DEPTH float near = czm_currentFrustum.x; float far = czm_currentFrustum.y; - logZ = pow(2.0, logZ * log2(far + 1.0)) - 1.0; + logZ = pow(2.0, logZ * czm_log2FarPlusOne) - 1.0; logZ = far * (1.0 - near / logZ) / (far - near); #endif return logZ; diff --git a/Source/Shaders/Builtin/Functions/vertexLogDepth.glsl b/Source/Shaders/Builtin/Functions/vertexLogDepth.glsl index 1a16d725f5c..d48609d6847 100644 --- a/Source/Shaders/Builtin/Functions/vertexLogDepth.glsl +++ b/Source/Shaders/Builtin/Functions/vertexLogDepth.glsl @@ -7,7 +7,7 @@ void czm_updatePositionDepth() { #if defined(LOG_DEPTH) && !defined(DISABLE_GL_POSITION_LOG_DEPTH) v_logPositionEC = (czm_inverseProjection * gl_Position).xyz; - gl_Position.z = log2(max(1e-6, 1.0 + gl_Position.w)) * czm_logFarDistance - 1.0; + gl_Position.z = log2(max(1e-6, 1.0 + gl_Position.w)) * czm_log2FarDistance - 1.0; gl_Position.z *= gl_Position.w; #endif } diff --git a/Source/Shaders/Builtin/Functions/writeLogDepth.glsl b/Source/Shaders/Builtin/Functions/writeLogDepth.glsl index 49d01bd2e72..b9511ef4b8e 100644 --- a/Source/Shaders/Builtin/Functions/writeLogDepth.glsl +++ b/Source/Shaders/Builtin/Functions/writeLogDepth.glsl @@ -19,9 +19,9 @@ varying float v_logZ; void czm_writeLogDepth(float logZ) { #if defined(GL_EXT_frag_depth) && defined(LOG_DEPTH) && !defined(DISABLE_LOG_DEPTH_FRAGMENT_WRITE) - float halfLogFarDistance = czm_logFarDistance * 0.5; + float halfLogFarDistance = czm_log2FarDistance * 0.5; float depth = log2(logZ); - if (depth < log2(czm_currentFrustum.x)) { + if (depth < czm_log2NearDistance) { discard; } gl_FragDepthEXT = depth * halfLogFarDistance; diff --git a/Specs/Renderer/AutomaticUniformSpec.js b/Specs/Renderer/AutomaticUniformSpec.js index f439c7e1af6..25778ed564f 100644 --- a/Specs/Renderer/AutomaticUniformSpec.js +++ b/Specs/Renderer/AutomaticUniformSpec.js @@ -1307,10 +1307,32 @@ defineSuite([ }).contextToRender(); }); - it('has czm_logFarDistance', function() { + it('has czm_log2FarDistance', function() { var fs = 'void main() {' + - ' gl_FragColor = vec4(czm_logFarDistance == (2.0 / log2(czm_currentFrustum.y + 1.0)));' + + ' gl_FragColor = vec4(czm_log2FarDistance == (2.0 / log2(czm_currentFrustum.y + 1.0)));' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + + it('has czm_log2FarPlusOne', function() { + var fs = + 'void main() {' + + ' gl_FragColor = vec4(czm_log2FarPlusOne == log2(czm_currentFrustum.y + 1.0));' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + + it('has czm_log2NearDistance', function() { + var fs = + 'void main() {' + + ' gl_FragColor = vec4(czm_log2NearDistance == log2(czm_currentFrustum.x));' + '}'; expect({ context : context, From 0844b9d52ba8c9abe4d7bb1b4731be832258bfd6 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 24 Apr 2018 14:59:27 -0400 Subject: [PATCH 22/40] cleanup for materials on GroundPrimitives [ci skip] --- .../StaticGroundGeometryPerMaterialBatch.js | 4 +- Source/Renderer/AutomaticUniforms.js | 3320 ++++++++--------- Source/Scene/ClassificationPrimitive.js | 389 +- Source/Scene/GroundPrimitive.js | 50 +- Source/Scene/ShadowVolumeAppearance.js | 900 +++++ Source/Scene/ShadowVolumeAppearanceShader.js | 474 --- 6 files changed, 2594 insertions(+), 2543 deletions(-) create mode 100644 Source/Scene/ShadowVolumeAppearance.js delete mode 100644 Source/Scene/ShadowVolumeAppearanceShader.js diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index d0c1971a121..d7e83022652 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -8,6 +8,7 @@ define([ '../Core/ShowGeometryInstanceAttribute', '../Core/RectangleCollisionChecker', '../Scene/GroundPrimitive', + '../Scene/ShadowVolumeAppearance', './BoundingSphereState', './ColorMaterialProperty', './MaterialProperty', @@ -22,6 +23,7 @@ define([ ShowGeometryInstanceAttribute, RectangleCollisionChecker, GroundPrimitive, + ShadowVolumeAppearance, BoundingSphereState, ColorMaterialProperty, MaterialProperty, @@ -318,7 +320,7 @@ define([ var items = this._items; var length = items.length; var geometryInstance = updater.createFillGeometryInstance(time); - var usingSphericalCoordinates = GroundPrimitive.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); + var usingSphericalCoordinates = ShadowVolumeAppearance.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); for (var i = 0; i < length; ++i) { var item = items[i]; if (item.isMaterial(updater) && diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 4264d4e1d81..4619017dc20 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1,1671 +1,1671 @@ define([ - '../Core/Cartesian3', - '../Core/Math', - '../Core/Matrix4', - '../Core/WebGLConstants' -], function( - Cartesian3, - CesiumMath, - Matrix4, - WebGLConstants) { -'use strict'; -/*global WebGLRenderingContext*/ - -var viewerPositionWCScratch = new Cartesian3(); - -function AutomaticUniform(options) { - this._size = options.size; - this._datatype = options.datatype; - this.getValue = options.getValue; -} - -// this check must use typeof, not defined, because defined doesn't work with undeclared variables. -if (typeof WebGLRenderingContext === 'undefined') { - return {}; -} - -var datatypeToGlsl = {}; -datatypeToGlsl[WebGLConstants.FLOAT] = 'float'; -datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2'; -datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3'; -datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4'; -datatypeToGlsl[WebGLConstants.INT] = 'int'; -datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2'; -datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3'; -datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4'; -datatypeToGlsl[WebGLConstants.BOOL] = 'bool'; -datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2'; -datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3'; -datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4'; -datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2'; -datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3'; -datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4'; -datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D'; -datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube'; - -AutomaticUniform.prototype.getDeclaration = function(name) { - var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name; - - var size = this._size; - if (size === 1) { - declaration += ';'; - } else { - declaration += '[' + size.toString() + '];'; + '../Core/Cartesian3', + '../Core/Math', + '../Core/Matrix4', + '../Core/WebGLConstants' + ], function( + Cartesian3, + CesiumMath, + Matrix4, + WebGLConstants) { + 'use strict'; + /*global WebGLRenderingContext*/ + + var viewerPositionWCScratch = new Cartesian3(); + + function AutomaticUniform(options) { + this._size = options.size; + this._datatype = options.datatype; + this.getValue = options.getValue; } - return declaration; -}; - -/** - * @private - */ -var AutomaticUniforms = { - /** - * An automatic GLSL uniform containing the viewport's x, y, width, - * and height properties in an vec4's x, y, z, - * and w components, respectively. - * - * @alias czm_viewport - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec4 czm_viewport; - * - * // Scale the window coordinate components to [0, 1] by dividing - * // by the viewport's width and height. - * vec2 v = gl_FragCoord.xy / czm_viewport.zw; - * - * @see Context#getViewport - */ - czm_viewport : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.viewportCartesian4; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 orthographic projection matrix that - * transforms window coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - *

- * This transform is useful when a vertex shader inputs or manipulates window coordinates - * as done by {@link BillboardCollection}. - *

- * Do not confuse {@link czm_viewportTransformation} with czm_viewportOrthographic. - * The former transforms from normalized device coordinates to window coordinates; the later transforms - * from window coordinates to clip coordinates, and is often used to assign to gl_Position. - * - * @alias czm_viewportOrthographic - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewportOrthographic; - * - * // Example - * gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0); - * - * @see UniformState#viewportOrthographic - * @see czm_viewport - * @see czm_viewportTransformation - * @see BillboardCollection - */ - czm_viewportOrthographic : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewportOrthographic; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms normalized device coordinates to window coordinates. The context's - * full viewport is used, and the depth range is assumed to be near = 0 - * and far = 1. - *

- * This transform is useful when there is a need to manipulate window coordinates - * in a vertex shader as done by {@link BillboardCollection}. In many cases, - * this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates} - * will be used to transform directly from model to window coordinates. - *

- * Do not confuse czm_viewportTransformation with {@link czm_viewportOrthographic}. - * The former transforms from normalized device coordinates to window coordinates; the later transforms - * from window coordinates to clip coordinates, and is often used to assign to gl_Position. - * - * @alias czm_viewportTransformation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewportTransformation; - * - * // Use czm_viewportTransformation as part of the - * // transform from model to window coordinates. - * vec4 q = czm_modelViewProjection * positionMC; // model to clip coordinates - * q.xyz /= q.w; // clip to normalized device coordinates (ndc) - * q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates - * - * @see UniformState#viewportTransformation - * @see czm_viewport - * @see czm_viewportOrthographic - * @see czm_modelToWindowCoordinates - * @see BillboardCollection - */ - czm_viewportTransformation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewportTransformation; - } - }), - - /** - * An automatic GLSL uniform representing the depth after - * only the globe has been rendered and packed into an RGBA texture. - * - * @private - * - * @alias czm_globeDepthTexture - * @glslUniform - * - * @example - * // GLSL declaration - * uniform sampler2D czm_globeDepthTexture; - * - * // Get the depth at the current fragment - * vec2 coords = gl_FragCoord.xy / czm_viewport.zw; - * float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); - */ - czm_globeDepthTexture : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_2D, - getValue : function(uniformState) { - return uniformState.globeDepthTexture; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model transformation matrix that - * transforms model coordinates to world coordinates. - * - * @alias czm_model - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_model; - * - * // Example - * vec4 worldPosition = czm_model * modelPosition; - * - * @see UniformState#model - * @see czm_inverseModel - * @see czm_modelView - * @see czm_modelViewProjection - */ - czm_model : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.model; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model transformation matrix that - * transforms world coordinates to model coordinates. - * - * @alias czm_inverseModel - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModel; - * - * // Example - * vec4 modelPosition = czm_inverseModel * worldPosition; - * - * @see UniformState#inverseModel - * @see czm_model - * @see czm_inverseModelView - */ - czm_inverseModel : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModel; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view transformation matrix that - * transforms world coordinates to eye coordinates. - * - * @alias czm_view - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_view; - * - * // Example - * vec4 eyePosition = czm_view * worldPosition; - * - * @see UniformState#view - * @see czm_viewRotation - * @see czm_modelView - * @see czm_viewProjection - * @see czm_modelViewProjection - * @see czm_inverseView - */ - czm_view : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.view; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view transformation matrix that - * transforms 3D world coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_view}, but in 2D and Columbus View it represents the view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_view3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_view3D; - * - * // Example - * vec4 eyePosition3D = czm_view3D * worldPosition3D; - * - * @see UniformState#view3D - * @see czm_view - */ - czm_view3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.view3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 view rotation matrix that - * transforms vectors in world coordinates to eye coordinates. - * - * @alias czm_viewRotation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_viewRotation; - * - * // Example - * vec3 eyeVector = czm_viewRotation * worldVector; - * - * @see UniformState#viewRotation - * @see czm_view - * @see czm_inverseView - * @see czm_inverseViewRotation - */ - czm_viewRotation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.viewRotation; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 view rotation matrix that - * transforms vectors in 3D world coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_viewRotation3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_viewRotation3D; - * - * // Example - * vec3 eyeVector = czm_viewRotation3D * worldVector; - * - * @see UniformState#viewRotation3D - * @see czm_viewRotation - */ - czm_viewRotation3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.viewRotation3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to world coordinates. - * - * @alias czm_inverseView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseView; - * - * // Example - * vec4 worldPosition = czm_inverseView * eyePosition; - * - * @see UniformState#inverseView - * @see czm_view - * @see czm_inverseNormal - */ - czm_inverseView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from 3D eye coordinates to world coordinates. In 3D mode, this is identical to - * {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseView3D; - * - * // Example - * vec4 worldPosition = czm_inverseView3D * eyePosition; - * - * @see UniformState#inverseView3D - * @see czm_inverseView - */ - czm_inverseView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that - * transforms vectors from eye coordinates to world coordinates. - * - * @alias czm_inverseViewRotation - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseViewRotation; - * - * // Example - * vec4 worldVector = czm_inverseViewRotation * eyeVector; - * - * @see UniformState#inverseView - * @see czm_view - * @see czm_viewRotation - * @see czm_inverseViewRotation - */ - czm_inverseViewRotation : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseViewRotation; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that - * transforms vectors from 3D eye coordinates to world coordinates. In 3D mode, this is identical to - * {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseViewRotation3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseViewRotation3D; - * - * // Example - * vec4 worldVector = czm_inverseViewRotation3D * eyeVector; - * - * @see UniformState#inverseView3D - * @see czm_inverseViewRotation - */ - czm_inverseViewRotation3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseViewRotation3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 projection transformation matrix that - * transforms eye coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_projection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_projection; - * - * // Example - * gl_Position = czm_projection * eyePosition; - * - * @see UniformState#projection - * @see czm_viewProjection - * @see czm_modelViewProjection - * @see czm_infiniteProjection - */ - czm_projection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.projection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that - * transforms from clip coordinates to eye coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseProjection; - * - * // Example - * vec4 eyePosition = czm_inverseProjection * clipPosition; - * - * @see UniformState#inverseProjection - * @see czm_projection - */ - czm_inverseProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity, - * that transforms eye coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. An infinite far plane is used - * in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles - * are not clipped by the far plane. - * - * @alias czm_infiniteProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_infiniteProjection; - * - * // Example - * gl_Position = czm_infiniteProjection * eyePosition; - * - * @see UniformState#infiniteProjection - * @see czm_projection - * @see czm_modelViewInfiniteProjection - */ - czm_infiniteProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.infiniteProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms model coordinates to eye coordinates. - *

- * Positions should be transformed to eye coordinates using czm_modelView and - * normals should be transformed using {@link czm_normal}. - * - * @alias czm_modelView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelView; - * - * // Example - * vec4 eyePosition = czm_modelView * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * vec4 eyePosition = czm_view * czm_model * modelPosition; - * - * @see UniformState#modelView - * @see czm_model - * @see czm_view - * @see czm_modelViewProjection - * @see czm_normal - */ - czm_modelView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms 3D model coordinates to eye coordinates. In 3D mode, this is identical to - * {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - *

- * Positions should be transformed to eye coordinates using czm_modelView3D and - * normals should be transformed using {@link czm_normal3D}. - * - * @alias czm_modelView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelView3D; - * - * // Example - * vec4 eyePosition = czm_modelView3D * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * vec4 eyePosition = czm_view3D * czm_model * modelPosition; - * - * @see UniformState#modelView3D - * @see czm_modelView - */ - czm_modelView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that - * transforms model coordinates, relative to the eye, to eye coordinates. This is used - * in conjunction with {@link czm_translateRelativeToEye}. - * - * @alias czm_modelViewRelativeToEye - * @glslUniform - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewRelativeToEye; - * - * // Example - * attribute vec3 positionHigh; - * attribute vec3 positionLow; - * - * void main() - * { - * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_projection * (czm_modelViewRelativeToEye * p); - * } - * - * @see czm_modelViewProjectionRelativeToEye - * @see czm_translateRelativeToEye - * @see EncodedCartesian3 - */ - czm_modelViewRelativeToEye : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewRelativeToEye; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to model coordinates. - * - * @alias czm_inverseModelView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelView; - * - * // Example - * vec4 modelPosition = czm_inverseModelView * eyePosition; - * - * @see UniformState#inverseModelView - * @see czm_modelView - */ - czm_inverseModelView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelView; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 transformation matrix that - * transforms from eye coordinates to 3D model coordinates. In 3D mode, this is identical to - * {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix - * as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseModelView3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelView3D; - * - * // Example - * vec4 modelPosition = czm_inverseModelView3D * eyePosition; - * - * @see UniformState#inverseModelView - * @see czm_inverseModelView - * @see czm_modelView3D - */ - czm_inverseModelView3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelView3D; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that - * transforms world coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_viewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_viewProjection; - * - * // Example - * vec4 gl_Position = czm_viewProjection * czm_model * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_projection * czm_view * czm_model * modelPosition; - * - * @see UniformState#viewProjection - * @see czm_view - * @see czm_projection - * @see czm_modelViewProjection - * @see czm_inverseViewProjection - */ - czm_viewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.viewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that - * transforms clip coordinates to world coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseViewProjection; - * - * // Example - * vec4 worldPosition = czm_inverseViewProjection * clipPosition; - * - * @see UniformState#inverseViewProjection - * @see czm_viewProjection - */ - czm_inverseViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_modelViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewProjection; - * - * // Example - * vec4 gl_Position = czm_modelViewProjection * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_projection * czm_view * czm_model * modelPosition; - * - * @see UniformState#modelViewProjection - * @see czm_model - * @see czm_view - * @see czm_projection - * @see czm_modelView - * @see czm_viewProjection - * @see czm_modelViewInfiniteProjection - * @see czm_inverseModelViewProjection - */ - czm_modelViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that - * transforms clip coordinates to model coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. - * - * @alias czm_inverseModelViewProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_inverseModelViewProjection; - * - * // Example - * vec4 modelPosition = czm_inverseModelViewProjection * clipPosition; - * - * @see UniformState#modelViewProjection - * @see czm_modelViewProjection - */ - czm_inverseModelViewProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.inverseModelViewProjection; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates, relative to the eye, to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. This is used in - * conjunction with {@link czm_translateRelativeToEye}. - * - * @alias czm_modelViewProjectionRelativeToEye - * @glslUniform - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewProjectionRelativeToEye; - * - * // Example - * attribute vec3 positionHigh; - * attribute vec3 positionLow; - * - * void main() - * { - * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); - * gl_Position = czm_modelViewProjectionRelativeToEye * p; - * } - * - * @see czm_modelViewRelativeToEye - * @see czm_translateRelativeToEye - * @see EncodedCartesian3 - */ - czm_modelViewProjectionRelativeToEye : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewProjectionRelativeToEye; - } - }), - - /** - * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that - * transforms model coordinates to clip coordinates. Clip coordinates is the - * coordinate system for a vertex shader's gl_Position output. The projection matrix places - * the far plane at infinity. This is useful in algorithms like shadow volumes and GPU ray casting with - * proxy geometry to ensure that triangles are not clipped by the far plane. - * - * @alias czm_modelViewInfiniteProjection - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat4 czm_modelViewInfiniteProjection; - * - * // Example - * vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition; - * - * // The above is equivalent to, but more efficient than: - * gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition; - * - * @see UniformState#modelViewInfiniteProjection - * @see czm_model - * @see czm_view - * @see czm_infiniteProjection - * @see czm_modelViewProjection - */ - czm_modelViewInfiniteProjection : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT4, - getValue : function(uniformState) { - return uniformState.modelViewInfiniteProjection; - } - }), - - /** - * An automatic GLSL uniform that indicates if the current camera is orthographic in 3D. - * - * @alias czm_orthographicIn3D - * @see UniformState#orthographicIn3D - */ - czm_orthographicIn3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.orthographicIn3D ? 1 : 0; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in model coordinates to eye coordinates. - *

- * Positions should be transformed to eye coordinates using {@link czm_modelView} and - * normals should be transformed using czm_normal. - * - * @alias czm_normal - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_normal; - * - * // Example - * vec3 eyeNormal = czm_normal * normal; - * - * @see UniformState#normal - * @see czm_inverseNormal - * @see czm_modelView - */ - czm_normal : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.normal; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in 3D model coordinates to eye coordinates. - * In 3D mode, this is identical to - * {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation - * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - *

- * Positions should be transformed to eye coordinates using {@link czm_modelView3D} and - * normals should be transformed using czm_normal3D. - * - * @alias czm_normal3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_normal3D; - * - * // Example - * vec3 eyeNormal = czm_normal3D * normal; - * - * @see UniformState#normal3D - * @see czm_normal - */ - czm_normal3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.normal3D; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in eye coordinates to model coordinates. This is - * the opposite of the transform provided by {@link czm_normal}. - * - * @alias czm_inverseNormal - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseNormal; - * - * // Example - * vec3 normalMC = czm_inverseNormal * normalEC; - * - * @see UniformState#inverseNormal - * @see czm_normal - * @see czm_modelView - * @see czm_inverseView - */ - czm_inverseNormal : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseNormal; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 normal transformation matrix that - * transforms normal vectors in eye coordinates to 3D model coordinates. This is - * the opposite of the transform provided by {@link czm_normal}. - * In 3D mode, this is identical to - * {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation - * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting - * 2D and Columbus View in the same way that 3D is lit. - * - * @alias czm_inverseNormal3D - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_inverseNormal3D; - * - * // Example - * vec3 normalMC = czm_inverseNormal3D * normalEC; - * - * @see UniformState#inverseNormal3D - * @see czm_inverseNormal - */ - czm_inverseNormal3D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.inverseNormal3D; - } - }), - - /** - * An automatic GLSL uniform containing height (x) and height squared (y) - * of the eye (camera) in the 2D scene in meters. - * - * @alias czm_eyeHeight2D - * @glslUniform - * - * @see UniformState#eyeHeight2D - */ - czm_eyeHeight2D : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.eyeHeight2D; - } - }), - - /** - * An automatic GLSL uniform containing the near distance (x) and the far distance (y) - * of the frustum defined by the camera. This is the largest possible frustum, not an individual - * frustum used for multi-frustum rendering. - * - * @alias czm_entireFrustum - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec2 czm_entireFrustum; - * - * // Example - * float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x; - * - * @see UniformState#entireFrustum - * @see czm_currentFrustum - */ - czm_entireFrustum : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.entireFrustum; - } - }), + // this check must use typeof, not defined, because defined doesn't work with undeclared variables. + if (typeof WebGLRenderingContext === 'undefined') { + return {}; + } - /** - * An automatic GLSL uniform containing the near distance (x) and the far distance (y) - * of the frustum defined by the camera. This is the individual - * frustum used for multi-frustum rendering. - * - * @alias czm_currentFrustum - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec2 czm_currentFrustum; - * - * // Example - * float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x; - * - * @see UniformState#currentFrustum - * @see czm_entireFrustum - */ - czm_currentFrustum : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC2, - getValue : function(uniformState) { - return uniformState.currentFrustum; + var datatypeToGlsl = {}; + datatypeToGlsl[WebGLConstants.FLOAT] = 'float'; + datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2'; + datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3'; + datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4'; + datatypeToGlsl[WebGLConstants.INT] = 'int'; + datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2'; + datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3'; + datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4'; + datatypeToGlsl[WebGLConstants.BOOL] = 'bool'; + datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2'; + datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3'; + datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4'; + datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2'; + datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3'; + datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4'; + datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D'; + datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube'; + + AutomaticUniform.prototype.getDeclaration = function(name) { + var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name; + + var size = this._size; + if (size === 1) { + declaration += ';'; + } else { + declaration += '[' + size.toString() + '];'; } - }), - /** - * The distances to the frustum planes. The top, bottom, left and right distances are - * the x, y, z, and w components, respectively. - * - * @alias czm_frustumPlanes - * @glslUniform - */ - czm_frustumPlanes : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.frustumPlanes; - } - }), + return declaration; + }; /** - * The log2 of the current frustums far plane. Used for computing the log depth. - * - * @alias czm_log2FarDistance - * @glslUniform - * * @private */ - czm_log2FarDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.logFarDistance; - } - }), - - /** - * An automatic GLSL uniform containing log2 of the far distance + 1.0. - * This is used when reversing log depth computations. - * - * @alias czm_log2FarPlusOne - * @glslUniform - */ - czm_log2FarPlusOne : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return CesiumMath.log2(uniformState.currentFrustum.y + 1.0); - } - }), - - /** - * An automatic GLSL uniform containing log2 of the near distance. - * This is used when writing log depth in the fragment shader. - * - * @alias czm_log2NearDistance - * @glslUniform - */ - czm_log2NearDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return CesiumMath.log2(uniformState.currentFrustum.x); - } - }), - - /** - * An automatic GLSL uniform representing the sun position in world coordinates. - * - * @alias czm_sunPositionWC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunPositionWC; - * - * @see UniformState#sunPositionWC - * @see czm_sunPositionColumbusView - * @see czm_sunDirectionWC - */ - czm_sunPositionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunPositionWC; - } - }), - - /** - * An automatic GLSL uniform representing the sun position in Columbus view world coordinates. - * - * @alias czm_sunPositionColumbusView - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunPositionColumbusView; - * - * @see UniformState#sunPositionColumbusView - * @see czm_sunPositionWC - */ - czm_sunPositionColumbusView : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunPositionColumbusView; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_sunDirectionEC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunDirectionEC; - * - * // Example - * float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0); - * - * @see UniformState#sunDirectionEC - * @see czm_moonDirectionEC - * @see czm_sunDirectionWC - */ - czm_sunDirectionEC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunDirectionEC; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the sun in world coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_sunDirectionWC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_sunDirectionWC; - * - * @see UniformState#sunDirectionWC - * @see czm_sunPositionWC - * @see czm_sunDirectionEC - */ - czm_sunDirectionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.sunDirectionWC; - } - }), - - /** - * An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates. - * This is commonly used for directional lighting computations. - * - * @alias czm_moonDirectionEC - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_moonDirectionEC; - * - * // Example - * float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0); - * - * @see UniformState#moonDirectionEC - * @see czm_sunDirectionEC - */ - czm_moonDirectionEC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.moonDirectionEC; - } - }), - - /** - * An automatic GLSL uniform representing the high bits of the camera position in model - * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering - * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. - * - * @alias czm_encodedCameraPositionMCHigh - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_encodedCameraPositionMCHigh; - * - * @see czm_encodedCameraPositionMCLow - * @see czm_modelViewRelativeToEye - * @see czm_modelViewProjectionRelativeToEye - */ - czm_encodedCameraPositionMCHigh : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.encodedCameraPositionMCHigh; - } - }), - - /** - * An automatic GLSL uniform representing the low bits of the camera position in model - * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering - * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. - * - * @alias czm_encodedCameraPositionMCLow - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform vec3 czm_encodedCameraPositionMCLow; - * - * @see czm_encodedCameraPositionMCHigh - * @see czm_modelViewRelativeToEye - * @see czm_modelViewProjectionRelativeToEye - */ - czm_encodedCameraPositionMCLow : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return uniformState.encodedCameraPositionMCLow; - } - }), - - /** - * An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates. - * - * @alias czm_viewerPositionWC - * @glslUniform - * - * @example - * // GLSL declaration - * uniform vec3 czm_viewerPositionWC; - */ - czm_viewerPositionWC : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC3, - getValue : function(uniformState) { - return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch); - } - }), - - /** - * An automatic GLSL uniform representing the frame number. This uniform is automatically incremented - * every frame. - * - * @alias czm_frameNumber - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_frameNumber; - */ - czm_frameNumber : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.frameNumber; - } - }), - - /** - * An automatic GLSL uniform representing the current morph transition time between - * 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D. - * - * @alias czm_morphTime - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_morphTime; - * - * // Example - * vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime); - */ - czm_morphTime : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.morphTime; - } - }), - - /** - * An automatic GLSL uniform representing the current {@link SceneMode}, expressed - * as a float. - * - * @alias czm_sceneMode - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform float czm_sceneMode; - * - * // Example - * if (czm_sceneMode == czm_sceneMode2D) - * { - * eyeHeightSq = czm_eyeHeight2D.y; - * } - * - * @see czm_sceneMode2D - * @see czm_sceneModeColumbusView - * @see czm_sceneMode3D - * @see czm_sceneModeMorphing - */ - czm_sceneMode : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.frameState.mode; - } - }), - - /** - * An automatic GLSL uniform representing the current rendering pass. - * - * @alias czm_pass - * @glslUniform - * - * @example - * // GLSL declaration - * uniform float czm_pass; - * - * // Example - * if ((czm_pass == czm_passTranslucent) && isOpaque()) - * { - * gl_Position *= 0.0; // Cull opaque geometry in the translucent pass - * } - */ - czm_pass : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.pass; - } - }), - - /** - * An automatic GLSL uniform representing the current scene background color. - * - * @alias czm_backgroundColor - * @glslUniform - * - * @example - * // GLSL declaration - * uniform vec4 czm_backgroundColor; - * - * // Example: If the given color's RGB matches the background color, invert it. - * vec4 adjustColorForContrast(vec4 color) - * { - * if (czm_backgroundColor.rgb == color.rgb) - * { - * color.rgb = vec3(1.0) - color.rgb; - * } - * - * return color; - * } - */ - czm_backgroundColor : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.backgroundColor; - } - }), - - /** - * An automatic GLSL uniform containing the BRDF look up texture used for image-based lighting computations. - * - * @alias czm_brdfLut - * @glslUniform - * - * @example - * // GLSL declaration - * uniform sampler2D czm_brdfLut; - * - * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels - * float roughness = 0.5; - * float NdotV = dot(normal, view); - * vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; - */ - czm_brdfLut : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_2D, - getValue : function(uniformState) { - return uniformState.brdfLut; - } - }), - - /** - * An automatic GLSL uniform containing the environment map used within the scene. - * - * @alias czm_environmentMap - * @glslUniform - * - * @example - * // GLSL declaration - * uniform samplerCube czm_environmentMap; - * - * // Example: Create a perfect reflection of the environment map on a model - * float reflected = reflect(view, normal); - * vec4 reflectedColor = textureCube(czm_environmentMap, reflected); - */ - czm_environmentMap : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.SAMPLER_CUBE, - getValue : function(uniformState) { - return uniformState.environmentMap; - } - }), - - /** - * An automatic GLSL uniform representing a 3x3 rotation matrix that transforms - * from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time. - * - * @alias czm_temeToPseudoFixed - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform mat3 czm_temeToPseudoFixed; - * - * // Example - * vec3 pseudoFixed = czm_temeToPseudoFixed * teme; - * - * @see UniformState#temeToPseudoFixedMatrix - * @see Transforms.computeTemeToPseudoFixedMatrix - */ - czm_temeToPseudoFixed : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_MAT3, - getValue : function(uniformState) { - return uniformState.temeToPseudoFixedMatrix; - } - }), - - /** - * An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space. - * - * @alias czm_resolutionScale - * @glslUniform - * - * @example - * uniform float czm_resolutionScale; - */ - czm_resolutionScale : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.resolutionScale; - } - }), - - /** - * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera. - * - * @alias czm_fogDensity - * @glslUniform - * - * @see czm_fog - */ - czm_fogDensity : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.fogDensity; - } - }), - - /** - * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. - * This will be in pixel coordinates relative to the canvas. - * - * @alias czm_imagerySplitPosition - * @glslUniform - * - * - * @example - * // GLSL declaration - * uniform float czm_imagerySplitPosition; - */ - czm_imagerySplitPosition : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.imagerySplitPosition; - } - }), - - /** - * An automatic GLSL uniform scalar representing the geometric tolerance per meter - * - * @alias czm_geometricToleranceOverMeter - * @glslUniform - */ - czm_geometricToleranceOverMeter : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.geometricToleranceOverMeter; - } - }), - - /** - * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points - * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, - * the depth test should never be applied. - * - * @alias czm_minimumDisableDepthTestDistance - * @glslUniform - */ - czm_minimumDisableDepthTestDistance : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT, - getValue : function(uniformState) { - return uniformState.minimumDisableDepthTestDistance; - } - }), - - /** - * An automatic GLSL uniform that will be the highlight color of unclassified 3D Tiles. - * - * @alias czm_invertClassificationColor - * @glslUniform - */ - czm_invertClassificationColor : new AutomaticUniform({ - size : 1, - datatype : WebGLConstants.FLOAT_VEC4, - getValue : function(uniformState) { - return uniformState.invertClassificationColor; - } - }) -}; - -return AutomaticUniforms; + var AutomaticUniforms = { + /** + * An automatic GLSL uniform containing the viewport's x, y, width, + * and height properties in an vec4's x, y, z, + * and w components, respectively. + * + * @alias czm_viewport + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec4 czm_viewport; + * + * // Scale the window coordinate components to [0, 1] by dividing + * // by the viewport's width and height. + * vec2 v = gl_FragCoord.xy / czm_viewport.zw; + * + * @see Context#getViewport + */ + czm_viewport : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.viewportCartesian4; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 orthographic projection matrix that + * transforms window coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + *

+ * This transform is useful when a vertex shader inputs or manipulates window coordinates + * as done by {@link BillboardCollection}. + *

+ * Do not confuse {@link czm_viewportTransformation} with czm_viewportOrthographic. + * The former transforms from normalized device coordinates to window coordinates; the later transforms + * from window coordinates to clip coordinates, and is often used to assign to gl_Position. + * + * @alias czm_viewportOrthographic + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewportOrthographic; + * + * // Example + * gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0); + * + * @see UniformState#viewportOrthographic + * @see czm_viewport + * @see czm_viewportTransformation + * @see BillboardCollection + */ + czm_viewportOrthographic : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewportOrthographic; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms normalized device coordinates to window coordinates. The context's + * full viewport is used, and the depth range is assumed to be near = 0 + * and far = 1. + *

+ * This transform is useful when there is a need to manipulate window coordinates + * in a vertex shader as done by {@link BillboardCollection}. In many cases, + * this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates} + * will be used to transform directly from model to window coordinates. + *

+ * Do not confuse czm_viewportTransformation with {@link czm_viewportOrthographic}. + * The former transforms from normalized device coordinates to window coordinates; the later transforms + * from window coordinates to clip coordinates, and is often used to assign to gl_Position. + * + * @alias czm_viewportTransformation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewportTransformation; + * + * // Use czm_viewportTransformation as part of the + * // transform from model to window coordinates. + * vec4 q = czm_modelViewProjection * positionMC; // model to clip coordinates + * q.xyz /= q.w; // clip to normalized device coordinates (ndc) + * q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates + * + * @see UniformState#viewportTransformation + * @see czm_viewport + * @see czm_viewportOrthographic + * @see czm_modelToWindowCoordinates + * @see BillboardCollection + */ + czm_viewportTransformation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewportTransformation; + } + }), + + /** + * An automatic GLSL uniform representing the depth after + * only the globe has been rendered and packed into an RGBA texture. + * + * @private + * + * @alias czm_globeDepthTexture + * @glslUniform + * + * @example + * // GLSL declaration + * uniform sampler2D czm_globeDepthTexture; + * + * // Get the depth at the current fragment + * vec2 coords = gl_FragCoord.xy / czm_viewport.zw; + * float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords)); + */ + czm_globeDepthTexture : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_2D, + getValue : function(uniformState) { + return uniformState.globeDepthTexture; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model transformation matrix that + * transforms model coordinates to world coordinates. + * + * @alias czm_model + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_model; + * + * // Example + * vec4 worldPosition = czm_model * modelPosition; + * + * @see UniformState#model + * @see czm_inverseModel + * @see czm_modelView + * @see czm_modelViewProjection + */ + czm_model : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.model; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model transformation matrix that + * transforms world coordinates to model coordinates. + * + * @alias czm_inverseModel + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModel; + * + * // Example + * vec4 modelPosition = czm_inverseModel * worldPosition; + * + * @see UniformState#inverseModel + * @see czm_model + * @see czm_inverseModelView + */ + czm_inverseModel : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModel; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view transformation matrix that + * transforms world coordinates to eye coordinates. + * + * @alias czm_view + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_view; + * + * // Example + * vec4 eyePosition = czm_view * worldPosition; + * + * @see UniformState#view + * @see czm_viewRotation + * @see czm_modelView + * @see czm_viewProjection + * @see czm_modelViewProjection + * @see czm_inverseView + */ + czm_view : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.view; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view transformation matrix that + * transforms 3D world coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_view}, but in 2D and Columbus View it represents the view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_view3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_view3D; + * + * // Example + * vec4 eyePosition3D = czm_view3D * worldPosition3D; + * + * @see UniformState#view3D + * @see czm_view + */ + czm_view3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.view3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 view rotation matrix that + * transforms vectors in world coordinates to eye coordinates. + * + * @alias czm_viewRotation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_viewRotation; + * + * // Example + * vec3 eyeVector = czm_viewRotation * worldVector; + * + * @see UniformState#viewRotation + * @see czm_view + * @see czm_inverseView + * @see czm_inverseViewRotation + */ + czm_viewRotation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.viewRotation; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 view rotation matrix that + * transforms vectors in 3D world coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_viewRotation3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_viewRotation3D; + * + * // Example + * vec3 eyeVector = czm_viewRotation3D * worldVector; + * + * @see UniformState#viewRotation3D + * @see czm_viewRotation + */ + czm_viewRotation3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.viewRotation3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to world coordinates. + * + * @alias czm_inverseView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseView; + * + * // Example + * vec4 worldPosition = czm_inverseView * eyePosition; + * + * @see UniformState#inverseView + * @see czm_view + * @see czm_inverseNormal + */ + czm_inverseView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from 3D eye coordinates to world coordinates. In 3D mode, this is identical to + * {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseView3D; + * + * // Example + * vec4 worldPosition = czm_inverseView3D * eyePosition; + * + * @see UniformState#inverseView3D + * @see czm_inverseView + */ + czm_inverseView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that + * transforms vectors from eye coordinates to world coordinates. + * + * @alias czm_inverseViewRotation + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseViewRotation; + * + * // Example + * vec4 worldVector = czm_inverseViewRotation * eyeVector; + * + * @see UniformState#inverseView + * @see czm_view + * @see czm_viewRotation + * @see czm_inverseViewRotation + */ + czm_inverseViewRotation : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseViewRotation; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that + * transforms vectors from 3D eye coordinates to world coordinates. In 3D mode, this is identical to + * {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseViewRotation3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseViewRotation3D; + * + * // Example + * vec4 worldVector = czm_inverseViewRotation3D * eyeVector; + * + * @see UniformState#inverseView3D + * @see czm_inverseViewRotation + */ + czm_inverseViewRotation3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseViewRotation3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 projection transformation matrix that + * transforms eye coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_projection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_projection; + * + * // Example + * gl_Position = czm_projection * eyePosition; + * + * @see UniformState#projection + * @see czm_viewProjection + * @see czm_modelViewProjection + * @see czm_infiniteProjection + */ + czm_projection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.projection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that + * transforms from clip coordinates to eye coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseProjection; + * + * // Example + * vec4 eyePosition = czm_inverseProjection * clipPosition; + * + * @see UniformState#inverseProjection + * @see czm_projection + */ + czm_inverseProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity, + * that transforms eye coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. An infinite far plane is used + * in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles + * are not clipped by the far plane. + * + * @alias czm_infiniteProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_infiniteProjection; + * + * // Example + * gl_Position = czm_infiniteProjection * eyePosition; + * + * @see UniformState#infiniteProjection + * @see czm_projection + * @see czm_modelViewInfiniteProjection + */ + czm_infiniteProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.infiniteProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms model coordinates to eye coordinates. + *

+ * Positions should be transformed to eye coordinates using czm_modelView and + * normals should be transformed using {@link czm_normal}. + * + * @alias czm_modelView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelView; + * + * // Example + * vec4 eyePosition = czm_modelView * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * vec4 eyePosition = czm_view * czm_model * modelPosition; + * + * @see UniformState#modelView + * @see czm_model + * @see czm_view + * @see czm_modelViewProjection + * @see czm_normal + */ + czm_modelView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms 3D model coordinates to eye coordinates. In 3D mode, this is identical to + * {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + *

+ * Positions should be transformed to eye coordinates using czm_modelView3D and + * normals should be transformed using {@link czm_normal3D}. + * + * @alias czm_modelView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelView3D; + * + * // Example + * vec4 eyePosition = czm_modelView3D * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * vec4 eyePosition = czm_view3D * czm_model * modelPosition; + * + * @see UniformState#modelView3D + * @see czm_modelView + */ + czm_modelView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that + * transforms model coordinates, relative to the eye, to eye coordinates. This is used + * in conjunction with {@link czm_translateRelativeToEye}. + * + * @alias czm_modelViewRelativeToEye + * @glslUniform + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewRelativeToEye; + * + * // Example + * attribute vec3 positionHigh; + * attribute vec3 positionLow; + * + * void main() + * { + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_projection * (czm_modelViewRelativeToEye * p); + * } + * + * @see czm_modelViewProjectionRelativeToEye + * @see czm_translateRelativeToEye + * @see EncodedCartesian3 + */ + czm_modelViewRelativeToEye : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewRelativeToEye; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to model coordinates. + * + * @alias czm_inverseModelView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelView; + * + * // Example + * vec4 modelPosition = czm_inverseModelView * eyePosition; + * + * @see UniformState#inverseModelView + * @see czm_modelView + */ + czm_inverseModelView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelView; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 transformation matrix that + * transforms from eye coordinates to 3D model coordinates. In 3D mode, this is identical to + * {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix + * as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseModelView3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelView3D; + * + * // Example + * vec4 modelPosition = czm_inverseModelView3D * eyePosition; + * + * @see UniformState#inverseModelView + * @see czm_inverseModelView + * @see czm_modelView3D + */ + czm_inverseModelView3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelView3D; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that + * transforms world coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_viewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_viewProjection; + * + * // Example + * vec4 gl_Position = czm_viewProjection * czm_model * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_projection * czm_view * czm_model * modelPosition; + * + * @see UniformState#viewProjection + * @see czm_view + * @see czm_projection + * @see czm_modelViewProjection + * @see czm_inverseViewProjection + */ + czm_viewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.viewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that + * transforms clip coordinates to world coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseViewProjection; + * + * // Example + * vec4 worldPosition = czm_inverseViewProjection * clipPosition; + * + * @see UniformState#inverseViewProjection + * @see czm_viewProjection + */ + czm_inverseViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_modelViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewProjection; + * + * // Example + * vec4 gl_Position = czm_modelViewProjection * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_projection * czm_view * czm_model * modelPosition; + * + * @see UniformState#modelViewProjection + * @see czm_model + * @see czm_view + * @see czm_projection + * @see czm_modelView + * @see czm_viewProjection + * @see czm_modelViewInfiniteProjection + * @see czm_inverseModelViewProjection + */ + czm_modelViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that + * transforms clip coordinates to model coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. + * + * @alias czm_inverseModelViewProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_inverseModelViewProjection; + * + * // Example + * vec4 modelPosition = czm_inverseModelViewProjection * clipPosition; + * + * @see UniformState#modelViewProjection + * @see czm_modelViewProjection + */ + czm_inverseModelViewProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.inverseModelViewProjection; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates, relative to the eye, to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. This is used in + * conjunction with {@link czm_translateRelativeToEye}. + * + * @alias czm_modelViewProjectionRelativeToEye + * @glslUniform + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewProjectionRelativeToEye; + * + * // Example + * attribute vec3 positionHigh; + * attribute vec3 positionLow; + * + * void main() + * { + * vec4 p = czm_translateRelativeToEye(positionHigh, positionLow); + * gl_Position = czm_modelViewProjectionRelativeToEye * p; + * } + * + * @see czm_modelViewRelativeToEye + * @see czm_translateRelativeToEye + * @see EncodedCartesian3 + */ + czm_modelViewProjectionRelativeToEye : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewProjectionRelativeToEye; + } + }), + + /** + * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that + * transforms model coordinates to clip coordinates. Clip coordinates is the + * coordinate system for a vertex shader's gl_Position output. The projection matrix places + * the far plane at infinity. This is useful in algorithms like shadow volumes and GPU ray casting with + * proxy geometry to ensure that triangles are not clipped by the far plane. + * + * @alias czm_modelViewInfiniteProjection + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat4 czm_modelViewInfiniteProjection; + * + * // Example + * vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition; + * + * // The above is equivalent to, but more efficient than: + * gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition; + * + * @see UniformState#modelViewInfiniteProjection + * @see czm_model + * @see czm_view + * @see czm_infiniteProjection + * @see czm_modelViewProjection + */ + czm_modelViewInfiniteProjection : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT4, + getValue : function(uniformState) { + return uniformState.modelViewInfiniteProjection; + } + }), + + /** + * An automatic GLSL uniform that indicates if the current camera is orthographic in 3D. + * + * @alias czm_orthographicIn3D + * @see UniformState#orthographicIn3D + */ + czm_orthographicIn3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.orthographicIn3D ? 1 : 0; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in model coordinates to eye coordinates. + *

+ * Positions should be transformed to eye coordinates using {@link czm_modelView} and + * normals should be transformed using czm_normal. + * + * @alias czm_normal + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_normal; + * + * // Example + * vec3 eyeNormal = czm_normal * normal; + * + * @see UniformState#normal + * @see czm_inverseNormal + * @see czm_modelView + */ + czm_normal : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.normal; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in 3D model coordinates to eye coordinates. + * In 3D mode, this is identical to + * {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation + * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + *

+ * Positions should be transformed to eye coordinates using {@link czm_modelView3D} and + * normals should be transformed using czm_normal3D. + * + * @alias czm_normal3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_normal3D; + * + * // Example + * vec3 eyeNormal = czm_normal3D * normal; + * + * @see UniformState#normal3D + * @see czm_normal + */ + czm_normal3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.normal3D; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in eye coordinates to model coordinates. This is + * the opposite of the transform provided by {@link czm_normal}. + * + * @alias czm_inverseNormal + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseNormal; + * + * // Example + * vec3 normalMC = czm_inverseNormal * normalEC; + * + * @see UniformState#inverseNormal + * @see czm_normal + * @see czm_modelView + * @see czm_inverseView + */ + czm_inverseNormal : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseNormal; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 normal transformation matrix that + * transforms normal vectors in eye coordinates to 3D model coordinates. This is + * the opposite of the transform provided by {@link czm_normal}. + * In 3D mode, this is identical to + * {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation + * matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting + * 2D and Columbus View in the same way that 3D is lit. + * + * @alias czm_inverseNormal3D + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_inverseNormal3D; + * + * // Example + * vec3 normalMC = czm_inverseNormal3D * normalEC; + * + * @see UniformState#inverseNormal3D + * @see czm_inverseNormal + */ + czm_inverseNormal3D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.inverseNormal3D; + } + }), + + /** + * An automatic GLSL uniform containing height (x) and height squared (y) + * of the eye (camera) in the 2D scene in meters. + * + * @alias czm_eyeHeight2D + * @glslUniform + * + * @see UniformState#eyeHeight2D + */ + czm_eyeHeight2D : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.eyeHeight2D; + } + }), + + /** + * An automatic GLSL uniform containing the near distance (x) and the far distance (y) + * of the frustum defined by the camera. This is the largest possible frustum, not an individual + * frustum used for multi-frustum rendering. + * + * @alias czm_entireFrustum + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec2 czm_entireFrustum; + * + * // Example + * float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x; + * + * @see UniformState#entireFrustum + * @see czm_currentFrustum + */ + czm_entireFrustum : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.entireFrustum; + } + }), + + /** + * An automatic GLSL uniform containing the near distance (x) and the far distance (y) + * of the frustum defined by the camera. This is the individual + * frustum used for multi-frustum rendering. + * + * @alias czm_currentFrustum + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec2 czm_currentFrustum; + * + * // Example + * float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x; + * + * @see UniformState#currentFrustum + * @see czm_entireFrustum + */ + czm_currentFrustum : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC2, + getValue : function(uniformState) { + return uniformState.currentFrustum; + } + }), + + /** + * The distances to the frustum planes. The top, bottom, left and right distances are + * the x, y, z, and w components, respectively. + * + * @alias czm_frustumPlanes + * @glslUniform + */ + czm_frustumPlanes : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.frustumPlanes; + } + }), + + /** + * The log2 of the current frustums far plane. Used for computing the log depth. + * + * @alias czm_log2FarDistance + * @glslUniform + * + * @private + */ + czm_log2FarDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.logFarDistance; + } + }), + + /** + * An automatic GLSL uniform containing log2 of the far distance + 1.0. + * This is used when reversing log depth computations. + * + * @alias czm_log2FarPlusOne + * @glslUniform + */ + czm_log2FarPlusOne : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return CesiumMath.log2(uniformState.currentFrustum.y + 1.0); + } + }), + + /** + * An automatic GLSL uniform containing log2 of the near distance. + * This is used when writing log depth in the fragment shader. + * + * @alias czm_log2NearDistance + * @glslUniform + */ + czm_log2NearDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return CesiumMath.log2(uniformState.currentFrustum.x); + } + }), + + /** + * An automatic GLSL uniform representing the sun position in world coordinates. + * + * @alias czm_sunPositionWC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunPositionWC; + * + * @see UniformState#sunPositionWC + * @see czm_sunPositionColumbusView + * @see czm_sunDirectionWC + */ + czm_sunPositionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunPositionWC; + } + }), + + /** + * An automatic GLSL uniform representing the sun position in Columbus view world coordinates. + * + * @alias czm_sunPositionColumbusView + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunPositionColumbusView; + * + * @see UniformState#sunPositionColumbusView + * @see czm_sunPositionWC + */ + czm_sunPositionColumbusView : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunPositionColumbusView; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_sunDirectionEC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunDirectionEC; + * + * // Example + * float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0); + * + * @see UniformState#sunDirectionEC + * @see czm_moonDirectionEC + * @see czm_sunDirectionWC + */ + czm_sunDirectionEC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunDirectionEC; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the sun in world coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_sunDirectionWC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_sunDirectionWC; + * + * @see UniformState#sunDirectionWC + * @see czm_sunPositionWC + * @see czm_sunDirectionEC + */ + czm_sunDirectionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.sunDirectionWC; + } + }), + + /** + * An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates. + * This is commonly used for directional lighting computations. + * + * @alias czm_moonDirectionEC + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_moonDirectionEC; + * + * // Example + * float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0); + * + * @see UniformState#moonDirectionEC + * @see czm_sunDirectionEC + */ + czm_moonDirectionEC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.moonDirectionEC; + } + }), + + /** + * An automatic GLSL uniform representing the high bits of the camera position in model + * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering + * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. + * + * @alias czm_encodedCameraPositionMCHigh + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_encodedCameraPositionMCHigh; + * + * @see czm_encodedCameraPositionMCLow + * @see czm_modelViewRelativeToEye + * @see czm_modelViewProjectionRelativeToEye + */ + czm_encodedCameraPositionMCHigh : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.encodedCameraPositionMCHigh; + } + }), + + /** + * An automatic GLSL uniform representing the low bits of the camera position in model + * coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering + * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}. + * + * @alias czm_encodedCameraPositionMCLow + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform vec3 czm_encodedCameraPositionMCLow; + * + * @see czm_encodedCameraPositionMCHigh + * @see czm_modelViewRelativeToEye + * @see czm_modelViewProjectionRelativeToEye + */ + czm_encodedCameraPositionMCLow : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return uniformState.encodedCameraPositionMCLow; + } + }), + + /** + * An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates. + * + * @alias czm_viewerPositionWC + * @glslUniform + * + * @example + * // GLSL declaration + * uniform vec3 czm_viewerPositionWC; + */ + czm_viewerPositionWC : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC3, + getValue : function(uniformState) { + return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch); + } + }), + + /** + * An automatic GLSL uniform representing the frame number. This uniform is automatically incremented + * every frame. + * + * @alias czm_frameNumber + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_frameNumber; + */ + czm_frameNumber : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.frameNumber; + } + }), + + /** + * An automatic GLSL uniform representing the current morph transition time between + * 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D. + * + * @alias czm_morphTime + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_morphTime; + * + * // Example + * vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime); + */ + czm_morphTime : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.morphTime; + } + }), + + /** + * An automatic GLSL uniform representing the current {@link SceneMode}, expressed + * as a float. + * + * @alias czm_sceneMode + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform float czm_sceneMode; + * + * // Example + * if (czm_sceneMode == czm_sceneMode2D) + * { + * eyeHeightSq = czm_eyeHeight2D.y; + * } + * + * @see czm_sceneMode2D + * @see czm_sceneModeColumbusView + * @see czm_sceneMode3D + * @see czm_sceneModeMorphing + */ + czm_sceneMode : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.frameState.mode; + } + }), + + /** + * An automatic GLSL uniform representing the current rendering pass. + * + * @alias czm_pass + * @glslUniform + * + * @example + * // GLSL declaration + * uniform float czm_pass; + * + * // Example + * if ((czm_pass == czm_passTranslucent) && isOpaque()) + * { + * gl_Position *= 0.0; // Cull opaque geometry in the translucent pass + * } + */ + czm_pass : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.pass; + } + }), + + /** + * An automatic GLSL uniform representing the current scene background color. + * + * @alias czm_backgroundColor + * @glslUniform + * + * @example + * // GLSL declaration + * uniform vec4 czm_backgroundColor; + * + * // Example: If the given color's RGB matches the background color, invert it. + * vec4 adjustColorForContrast(vec4 color) + * { + * if (czm_backgroundColor.rgb == color.rgb) + * { + * color.rgb = vec3(1.0) - color.rgb; + * } + * + * return color; + * } + */ + czm_backgroundColor : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.backgroundColor; + } + }), + + /** + * An automatic GLSL uniform containing the BRDF look up texture used for image-based lighting computations. + * + * @alias czm_brdfLut + * @glslUniform + * + * @example + * // GLSL declaration + * uniform sampler2D czm_brdfLut; + * + * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels + * float roughness = 0.5; + * float NdotV = dot(normal, view); + * vec2 brdfLut = texture2D(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; + */ + czm_brdfLut : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_2D, + getValue : function(uniformState) { + return uniformState.brdfLut; + } + }), + + /** + * An automatic GLSL uniform containing the environment map used within the scene. + * + * @alias czm_environmentMap + * @glslUniform + * + * @example + * // GLSL declaration + * uniform samplerCube czm_environmentMap; + * + * // Example: Create a perfect reflection of the environment map on a model + * float reflected = reflect(view, normal); + * vec4 reflectedColor = textureCube(czm_environmentMap, reflected); + */ + czm_environmentMap : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.SAMPLER_CUBE, + getValue : function(uniformState) { + return uniformState.environmentMap; + } + }), + + /** + * An automatic GLSL uniform representing a 3x3 rotation matrix that transforms + * from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time. + * + * @alias czm_temeToPseudoFixed + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform mat3 czm_temeToPseudoFixed; + * + * // Example + * vec3 pseudoFixed = czm_temeToPseudoFixed * teme; + * + * @see UniformState#temeToPseudoFixedMatrix + * @see Transforms.computeTemeToPseudoFixedMatrix + */ + czm_temeToPseudoFixed : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_MAT3, + getValue : function(uniformState) { + return uniformState.temeToPseudoFixedMatrix; + } + }), + + /** + * An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space. + * + * @alias czm_resolutionScale + * @glslUniform + * + * @example + * uniform float czm_resolutionScale; + */ + czm_resolutionScale : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.resolutionScale; + } + }), + + /** + * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera. + * + * @alias czm_fogDensity + * @glslUniform + * + * @see czm_fog + */ + czm_fogDensity : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.fogDensity; + } + }), + + /** + * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter. + * This will be in pixel coordinates relative to the canvas. + * + * @alias czm_imagerySplitPosition + * @glslUniform + * + * + * @example + * // GLSL declaration + * uniform float czm_imagerySplitPosition; + */ + czm_imagerySplitPosition : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.imagerySplitPosition; + } + }), + + /** + * An automatic GLSL uniform scalar representing the geometric tolerance per meter + * + * @alias czm_geometricToleranceOverMeter + * @glslUniform + */ + czm_geometricToleranceOverMeter : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.geometricToleranceOverMeter; + } + }), + + /** + * An automatic GLSL uniform representing the distance from the camera at which to disable the depth test of billboards, labels and points + * to, for example, prevent clipping against terrain. When set to zero, the depth test should always be applied. When less than zero, + * the depth test should never be applied. + * + * @alias czm_minimumDisableDepthTestDistance + * @glslUniform + */ + czm_minimumDisableDepthTestDistance : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.minimumDisableDepthTestDistance; + } + }), + + /** + * An automatic GLSL uniform that will be the highlight color of unclassified 3D Tiles. + * + * @alias czm_invertClassificationColor + * @glslUniform + */ + czm_invertClassificationColor : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT_VEC4, + getValue : function(uniformState) { + return uniformState.invertClassificationColor; + } + }) + }; + + return AutomaticUniforms; }); diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 2bbe9461f25..450cb78bfb8 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -1,23 +1,12 @@ define([ - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/Math', - '../Core/Check', '../Core/ColorGeometryInstanceAttribute', '../Core/combine', - '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', - '../Core/EncodedCartesian3', '../Core/GeometryInstance', - '../Core/GeometryInstanceAttribute', - '../Core/Matrix2', - '../Core/Rectangle', - '../Core/WebGLConstants', '../Core/isArray', '../Renderer/DrawCommand', '../Renderer/Pass', @@ -30,34 +19,21 @@ define([ './BlendingState', './ClassificationType', './DepthFunction', - './Material', - './MaterialAppearance', './PerInstanceColorAppearance', './Primitive', './SceneMode', - './ShadowVolumeAppearanceShader', + './ShadowVolumeAppearance', './StencilFunction', './StencilOperation' ], function( - Cartesian2, - Cartesian3, - Cartographic, - CesiumMath, - Check, ColorGeometryInstanceAttribute, combine, - ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, DeveloperError, - EncodedCartesian3, GeometryInstance, - GeometryInstanceAttribute, - Matrix2, - Rectangle, - WebGLConstants, isArray, DrawCommand, Pass, @@ -70,12 +46,10 @@ define([ BlendingState, ClassificationType, DepthFunction, - Material, - MaterialAppearance, PerInstanceColorAppearance, Primitive, SceneMode, - ShadowVolumeAppearanceShader, + ShadowVolumeAppearance, StencilFunction, StencilOperation) { 'use strict'; @@ -89,6 +63,9 @@ define([ * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. + * Only the {@link PerInstanceColorAppearance} is supported at this time when using ClassificationPrimitive directly. + * For full {@link Appearance} support use {@link GroundPrimitive} instead. + * *

*

* For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there @@ -229,12 +206,12 @@ define([ } else if (hasPerColorAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); } - if (defined(attributes.sphericalExtents)) { + if (ShadowVolumeAppearance.hasAttributesForSphericalExtents) { hasSphericalExtentsAttribute = true; } else if (hasSphericalExtentsAttribute) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); } - if (hasAttributesForTextureCoordinatePlanes(attributes)) { + if (ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes)) { hasPlanarExtentsAttributes = true; } else if (hasPlanarExtentsAttributes) { throw new DeveloperError('All GeometryInstances must have the same attributes.'); @@ -250,11 +227,11 @@ define([ flat : true }); } + if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); } - // TODO: SphericalExtents or PlanarExtents needed if PerInstanceColor isn't all the same if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute or GeometryInstanceAttributes for computing planes'); } @@ -598,8 +575,8 @@ define([ vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); vs = Primitive._updateColorAttribute(primitive, vs); - var usePlanarExtents = classificationPrimitive._hasPlanarExtentsAttributes; - var cullUsingExtents = classificationPrimitive._hasPlanarExtentsAttributes || classificationPrimitive._hasSphericalExtentsAttribute; + var planarExtents = classificationPrimitive._hasPlanarExtentsAttributes; + var cullUsingExtents = planarExtents || classificationPrimitive._hasSphericalExtentsAttribute; if (classificationPrimitive._extruded) { vs = modifyForEncodedNormals(primitive, vs); @@ -620,6 +597,8 @@ define([ }); var attributeLocations = classificationPrimitive._primitive._attributeLocations; + var shadowVolumeAppearance = new ShadowVolumeAppearance(cullUsingExtents, planarExtents, classificationPrimitive.appearance); + classificationPrimitive._spStencil = ShaderProgram.replaceCache({ context : context, shaderProgram : classificationPrimitive._spStencil, @@ -633,15 +612,14 @@ define([ vsPick = Primitive._appendShowToShader(primitive, vsPick); vsPick = Primitive._updatePickColorAttribute(vsPick); - var pick3DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, false, vsPick); var pickFS3D = new ShaderSource({ - sources : [pick3DShadowVolumeAppearanceShader.fragmentShaderSource], + sources : [shadowVolumeAppearance.createPickingFragmentShader(false)], pickColorQualifier : 'varying' }); var pickVS3D = new ShaderSource({ defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [pick3DShadowVolumeAppearanceShader.vertexShaderSource] + sources : [shadowVolumeAppearance.createVertexShader(vsPick, false)] }); classificationPrimitive._spPick = ShaderProgram.replaceCache({ @@ -652,15 +630,14 @@ define([ attributeLocations : attributeLocations }); - var pick2DShadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, true, true, vsPick); var pickFS2D = new ShaderSource({ - sources : [pick2DShadowVolumeAppearanceShader.fragmentShaderSource], + sources : [shadowVolumeAppearance.createPickingFragmentShader(true)], pickColorQualifier : 'varying' }); var pickVS2D = new ShaderSource({ defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [pick2DShadowVolumeAppearanceShader.vertexShaderSource] + sources : [shadowVolumeAppearance.createVertexShader(vsPick, true)] }); classificationPrimitive._spPick2D = ShaderProgram.replaceCache({ @@ -693,27 +670,14 @@ define([ attributeLocations : attributeLocations }); - var appearance = classificationPrimitive.appearance; - var isPerInstanceColor = appearance instanceof PerInstanceColorAppearance; - - var parts; - // Create a fragment shader that computes only required material hookups using screen space techniques - var shadowVolumeAppearanceShader = new ShadowVolumeAppearanceShader(cullUsingExtents, usePlanarExtents, false, vs, appearance); - var shadowVolumeAppearanceFS = shadowVolumeAppearanceShader.fragmentShaderSource; - if (isPerInstanceColor) { - parts = [shadowVolumeAppearanceFS]; - } else { - parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS]; - } - var fsColorSource = new ShaderSource({ - sources : [parts.join('\n')] + sources : [shadowVolumeAppearance.createAppearanceFragmentShader(false)] }); var vsColorSource = new ShaderSource({ defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearanceShader.vertexShaderSource] + sources : [shadowVolumeAppearance.createVertexShader(vs, false)] }); classificationPrimitive._spColor = ShaderProgram.replaceCache({ @@ -725,21 +689,13 @@ define([ }); // Create a similar fragment shader for 2D, forcing planar extents - var shadowVolumeAppearanceShader2D = new ShadowVolumeAppearanceShader(cullUsingExtents, true, true, vs, appearance); - var shadowVolumeAppearanceFS2D = shadowVolumeAppearanceShader2D.fragmentShaderSource; - if (isPerInstanceColor) { - parts = [shadowVolumeAppearanceFS2D]; - } else { - parts = [appearance.material.shaderSource, shadowVolumeAppearanceFS2D]; - } - var fsColorSource2D = new ShaderSource({ - sources : [parts.join('\n')] + sources : [shadowVolumeAppearance.createAppearanceFragmentShader(true)] }); var vsColorSource2D = new ShaderSource({ defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearanceShader2D.vertexShaderSource] + sources : [shadowVolumeAppearance.createVertexShader(vs, true)] }); classificationPrimitive._spColor2D = ShaderProgram.replaceCache({ @@ -1171,310 +1127,5 @@ define([ return destroyObject(this); }; - function hasAttributesForTextureCoordinatePlanes(attributes) { - return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && - defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && - defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW); - } - - var encodeScratch = new EncodedCartesian3(); - function addAttributesForPoint(point, name, attributes) { - var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); - - attributes[name + '_HIGH'] = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(encoded.high, [0, 0, 0]) - }); - - attributes[name + '_LOW'] = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(encoded.low, [0, 0, 0]) - }); - } - - var cartographicScratch = new Cartographic(); - var rectangleCenterScratch = new Cartographic(); - var northCenterScratch = new Cartesian3(); - var southCenterScratch = new Cartesian3(); - var eastCenterScratch = new Cartesian3(); - var westCenterScratch = new Cartesian3(); - var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; - var rotation2DScratch = new Matrix2(); - var min2DScratch = new Cartesian2(); - var max2DScratch = new Cartesian2(); - function getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) { - var theta = defaultValue(textureCoordinateRotation, 0.0); - - // Approximate scale such that the rectangle, if scaled and rotated, will completely enclose - // the unrotated/unscaled rectangle. - var cosTheta = Math.cos(theta); - var sinTheta = Math.sin(theta); - - // Build a rectangle centered in 2D space approximating the bounding rectangle's dimensions - var cartoCenter = Rectangle.center(rectangle, rectangleCenterScratch); - - var carto = cartographicScratch; - carto.latitude = cartoCenter.latitude; - - carto.longitude = rectangle.west; - var westCenter = Cartographic.toCartesian(carto, ellipsoid, westCenterScratch); - - carto.longitude = rectangle.east; - var eastCenter = Cartographic.toCartesian(carto, ellipsoid, eastCenterScratch); - - carto.longitude = cartoCenter.longitude; - carto.latitude = rectangle.north; - var northCenter = Cartographic.toCartesian(carto, ellipsoid, northCenterScratch); - - carto.latitude = rectangle.south; - var southCenter = Cartographic.toCartesian(carto, ellipsoid, southCenterScratch); - - var northSouthHalfDistance = Cartesian3.distance(northCenter, southCenter) * 0.5; - var eastWestHalfDistance = Cartesian3.distance(eastCenter, westCenter) * 0.5; - - var points2D = points2DScratch; - points2D[0].x = eastWestHalfDistance; - points2D[0].y = northSouthHalfDistance; - - points2D[1].x = -eastWestHalfDistance; - points2D[1].y = northSouthHalfDistance; - - points2D[2].x = eastWestHalfDistance; - points2D[2].y = -northSouthHalfDistance; - - points2D[3].x = -eastWestHalfDistance; - points2D[3].y = -northSouthHalfDistance; - - // Rotate the dimensions rectangle and compute min/max in rotated space - var min2D = min2DScratch; - min2D.x = Number.POSITIVE_INFINITY; - min2D.y = Number.POSITIVE_INFINITY; - var max2D = max2DScratch; - max2D.x = Number.NEGATIVE_INFINITY; - max2D.y = Number.NEGATIVE_INFINITY; - - var rotation2D = Matrix2.fromRotation(-theta, rotation2DScratch); - for (var i = 0; i < 4; ++i) { - var point2D = points2D[i]; - Matrix2.multiplyByVector(rotation2D, point2D, point2D); - Cartesian2.minimumByComponent(point2D, min2D, min2D); - Cartesian2.maximumByComponent(point2D, max2D, max2D); - } - - // Depending on the rotation, east/west may be more appropriate for vertical scale than horizontal - var scaleU, scaleV; - if (Math.abs(sinTheta) < Math.abs(cosTheta)) { - scaleU = eastWestHalfDistance / ((max2D.x - min2D.x) * 0.5); - scaleV = northSouthHalfDistance / ((max2D.y - min2D.y) * 0.5); - } else { - scaleU = eastWestHalfDistance / ((max2D.y - min2D.y) * 0.5); - scaleV = northSouthHalfDistance / ((max2D.x - min2D.x) * 0.5); - } - - return new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 4, - normalize: false, - value : [sinTheta, cosTheta, scaleU, scaleV] // Precompute trigonometry for rotation and inverse of scale - }); - } - - // Swizzle to 2D/CV projected space. This produces positions that - // can directly be used in the VS for 2D/CV, but in practice there's - // a lot of duplicated and zero values (see below). - var swizzleScratch = new Cartesian3(); - function swizzle(cartesian) { - Cartesian3.clone(cartesian, swizzleScratch); - cartesian.x = swizzleScratch.z; - cartesian.y = swizzleScratch.x; - cartesian.z = swizzleScratch.y; - return cartesian; - } - - var cornerScratch = new Cartesian3(); - var northWestScratch = new Cartesian3(); - var southEastScratch = new Cartesian3(); - var highLowScratch = {high : 0.0, low : 0.0}; - function add2DTextureCoordinateAttributes(rectangle, frameState, attributes) { - var projection = frameState.mapProjection; - - // Compute corner positions in double precision - var carto = cartographicScratch; - carto.height = 0.0; - - carto.longitude = rectangle.west; - carto.latitude = rectangle.south; - - var southWestCorner = swizzle(projection.project(carto, cornerScratch)); - - carto.latitude = rectangle.north; - var northWest = swizzle(projection.project(carto, northWestScratch)); - - carto.longitude = rectangle.east; - carto.latitude = rectangle.south; - var southEast = swizzle(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: y value for southWestCorner - // y: z value for southWestCorner - // z: z value for northWest - // w: y value for southEast - var valuesHigh = [0, 0, 0, 0]; - var valuesLow = [0, 0, 0, 0]; - var encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); - valuesHigh[0] = encoded.high; - valuesLow[0] = encoded.low; - - encoded = EncodedCartesian3.encode(southWestCorner.z, highLowScratch); - valuesHigh[1] = encoded.high; - valuesLow[1] = encoded.low; - - encoded = EncodedCartesian3.encode(northWest.z, highLowScratch); - valuesHigh[2] = encoded.high; - valuesLow[2] = encoded.low; - - encoded = EncodedCartesian3.encode(southEast.y, 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 - }); - } - - /** - * Gets an attributes object containing 3 high-precision points as 6 GeometryInstanceAttributes. - * These points are used to compute eye-space planes, which are then used to compute texture - * coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances. - * @see ShadowVolumeAppearanceShader - * @private - * - * @param {Rectangle} rectangle Rectangle object that the points will approximately bound - * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates - * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation - * @return {Object} An attributes dictionary containing planar texture coordinate attributes. - */ - ClassificationPrimitive.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, frameState, textureCoordinateRotation) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - Check.typeOf.object('ellipsoid', ellipsoid); - Check.typeOf.object('frameState', frameState); - //>>includeEnd('debug'); - - // Compute corner positions in double precision - var carto = cartographicScratch; - carto.height = 0.0; - - carto.longitude = rectangle.west; - carto.latitude = rectangle.south; - - var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); - - carto.latitude = rectangle.north; - var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); - - carto.longitude = rectangle.east; - carto.latitude = rectangle.south; - var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); - - var attributes = { - stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) - }; - addAttributesForPoint(corner, 'southWest', attributes); - addAttributesForPoint(northWest, 'northWest', attributes); - addAttributesForPoint(southEast, 'southEast', attributes); - - add2DTextureCoordinateAttributes(rectangle, frameState, attributes); - 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. - * Used when computing texture coordinates for large-area ClassificationPrimitives with materials or - * multiple non-overlapping instances. - * @see ShadowVolumeAppearanceShader - * @private - * - * @param {Rectangle} rectangle Rectangle object that the spherical extents will approximately bound - * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates - * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation - * @return An attributes dictionary containing spherical texture coordinate attributes. - */ - ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, frameState, textureCoordinateRotation) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); - Check.typeOf.object('ellipsoid', ellipsoid); - Check.typeOf.object('frameState', frameState); - //>>includeEnd('debug'); - - // rectangle cartographic coords !== spherical because it's on an ellipsoid - var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, ellipsoid, sphericalScratch); - - // Slightly pad extents to avoid floating point error when fragment culling at edges. - var south = southWestExtents.x - CesiumMath.EPSILON5; - var west = southWestExtents.y - CesiumMath.EPSILON5; - - var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, ellipsoid, sphericalScratch); - var north = northEastExtents.x + CesiumMath.EPSILON5; - var east = northEastExtents.y + 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] - }), - stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) - }; - - add2DTextureCoordinateAttributes(rectangle, frameState, attributes); - return attributes; - }; - return ClassificationPrimitive; }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index f4143fc4d69..5c0fbaaab9c 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -3,9 +3,7 @@ define([ '../Core/buildModuleUrl', '../Core/Cartesian2', '../Core/Cartesian3', - '../Core/Cartesian4', '../Core/Cartographic', - '../Core/Check', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -24,15 +22,14 @@ define([ './ClassificationPrimitive', './ClassificationType', './PerInstanceColorAppearance', - './SceneMode' + './SceneMode', + './ShadowVolumeAppearance' ], function( BoundingSphere, buildModuleUrl, Cartesian2, Cartesian3, - Cartesian4, Cartographic, - Check, defaultValue, defined, defineProperties, @@ -51,7 +48,8 @@ define([ ClassificationPrimitive, ClassificationType, PerInstanceColorAppearance, - SceneMode) { + SceneMode, + ShadowVolumeAppearance) { 'use strict'; /** @@ -61,7 +59,10 @@ define([ * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. - * // TODO add note that textures on ground should use single imagery provider for high precision + * + * Textured GroundPrimitives were designed for notional patterns and are not meant for precisely mapping + * textures to terrain - for that use case, use {@link SingleTileImageryProvider}. + * *

*

* For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there @@ -800,7 +801,7 @@ define([ instance = instances[i]; geometry = instance.geometry; rectangle = getRectangle(frameState, geometry); - if (shouldUseSpherical(rectangle)) { + if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) { usePlanarExtents = false; break; } @@ -815,9 +816,9 @@ define([ var attributes; if (usePlanarExtents) { - attributes = ClassificationPrimitive.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState, geometry._stRotation); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); } else { - attributes = ClassificationPrimitive.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState, geometry._stRotation); + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); } var instanceAttributes = instance.attributes; @@ -869,10 +870,6 @@ define([ this._primitive.update(frameState); }; - function shouldUseSpherical(rectangle) { - return Math.max(rectangle.width, rectangle.height) > GroundPrimitive.MAX_WIDTH_FOR_PLANAR_EXTENTS; - } - /** * @private */ @@ -943,30 +940,5 @@ define([ return destroyObject(this); }; - /** - * 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 - */ - GroundPrimitive.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 - */ - GroundPrimitive.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0); - return GroundPrimitive; }); diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js new file mode 100644 index 00000000000..d2ab636160d --- /dev/null +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -0,0 +1,900 @@ +define([ + '../Core/Cartographic', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Math', + '../Core/Check', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/EncodedCartesian3', + '../Core/GeometryInstanceAttribute', + '../Core/Matrix2', + '../Core/Rectangle', + '../Renderer/ShaderSource', + '../Scene/PerInstanceColorAppearance' +], function( + Cartographic, + Cartesian2, + Cartesian3, + CesiumMath, + Check, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + EncodedCartesian3, + GeometryInstanceAttribute, + Matrix2, + Rectangle, + ShaderSource, + PerInstanceColorAppearance) { + 'use strict'; + + /** + * 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'); + + // Compute shader dependencies + var shaderDependencies = new ShaderDependencies(); + shaderDependencies.requiresTextureCoordinates = extentsCulling; + shaderDependencies.requiresEC = !appearance.flat; + + if (appearance instanceof PerInstanceColorAppearance) { + // PerInstanceColorAppearance doesn't have material.shaderSource, instead it has its own vertex and fragment shaders + shaderDependencies.requiresNormalEC = !appearance.flat; + } else { + // Scan material source for what hookups are needed. Assume czm_materialInput materialInput. + var materialShaderSource = appearance.material.shaderSource; + + shaderDependencies.normalEC = materialShaderSource.includes('materialInput.normalEC') || materialShaderSource.includes('czm_getDefaultMaterial'); + shaderDependencies.positionToEyeEC = materialShaderSource.includes('materialInput.positionToEyeEC'); + shaderDependencies.tangentToEyeMatrix = materialShaderSource.includes('materialInput.tangentToEyeMatrix'); + shaderDependencies.st = materialShaderSource.includes('materialInput.st'); + } + + this._shaderDependencies = shaderDependencies; + 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 {String} Shader source for the fragment shader including its material. + */ + ShadowVolumeAppearance.prototype.createAppearanceFragmentShader = function(columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); + + var appearance = this._appearance; + var materialHookups = createShadowVolumeAppearanceFS(this._shaderDependencies, appearance, this._extentsCulling, this._planarExtents || columbusView2D); + if (appearance instanceof PerInstanceColorAppearance) { + return materialHookups; + } + return appearance.material.shaderSource + '\n' + materialHookups; + }; + + /** + * Create the fragment shader for a ClassificationPrimitive's color pass when rendering for pick. + * + * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. + * @returns {String} Shader source for the fragment shader. + */ + ShadowVolumeAppearance.prototype.createPickingFragmentShader = function(columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); + + return getPickShaderFS(this._extentsCulling, this._planarExtents || columbusView2D); + }; + + /** + * Create the vertex shader for a ClassificationPrimitive's color pass, both when rendering for color and for pick. + * + * @param {String} vertexShaderSource Vertex shader source. + * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. + * @returns {String} Shader source for the vertex shader. + */ + ShadowVolumeAppearance.prototype.createVertexShader = function(vertexShaderSource, columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string('vertexShaderSource', vertexShaderSource); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); + + return createShadowVolumeAppearanceVS(this._shaderDependencies, this._appearance, this._planarExtents, columbusView2D, vertexShaderSource); + }; + + function createShadowVolumeAppearanceFS(shaderDependencies, appearance, extentsCull, planarExtents) { + if (appearance instanceof PerInstanceColorAppearance) { + return getPerInstanceColorShaderFS(shaderDependencies, extentsCull, appearance.flat, planarExtents); + } + + var usesNormalEC = shaderDependencies.normalEC; + var usesPositionToEyeEC = shaderDependencies.positionToEyeEC; + var usesTangentToEyeMat = shaderDependencies.tangentToEyeMatrix; + var usesSt = shaderDependencies.st; + + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n'; + if (extentsCull || usesSt) { + glsl += planarExtents ? + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' : + + 'varying vec4 v_sphericalExtents;\n'; + } + if (usesSt) { + glsl += + 'varying vec4 v_stSineCosineUVScale;\n'; + } + + // Get local functions + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); + + glsl += + 'void main(void)\n' + + '{\n'; + + // Compute material input stuff and cull if outside texture coordinate extents + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCull, planarExtents); + + glsl += ' czm_materialInput materialInput;\n'; + if (usesNormalEC) { + glsl += ' materialInput.normalEC = normalEC;\n'; + } + if (usesPositionToEyeEC) { + glsl += ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n'; + } + if (usesTangentToEyeMat) { + glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC);\n'; + } + if (usesSt) { + // Scale texture coordinates and rotate around 0.5, 0.5 + glsl += + ' materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5;\n' + + ' materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5;\n'; + } + glsl += ' czm_material material = czm_getMaterial(materialInput);\n'; + + if (appearance.flat) { + glsl += ' gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n'; + } else { + glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; + } + glsl += ' czm_writeDepthClampedToFarPlane();\n'; + glsl += '}\n'; + return glsl; + } + + var pickingShaderDependenciesScratch = new ShaderDependencies(); + function getPickShaderFS(extentsCulling, planarExtents) { + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n'; + if (extentsCulling) { + glsl += planarExtents ? + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' : + + 'varying vec4 v_sphericalExtents;\n'; + } + var shaderDependencies = pickingShaderDependenciesScratch; + shaderDependencies.reset(); + shaderDependencies.requiresTextureCoordinates = extentsCulling; + shaderDependencies.requiresNormalEC = false; + + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); + + glsl += 'void main(void)\n' + + '{\n'; + glsl += ' bool culled = false;\n'; + var outOfBoundsSnippet = + ' culled = true;\n'; + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); + glsl += ' if (!culled) {\n' + + ' gl_FragColor.a = 1.0;\n' + // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource + ' czm_writeDepthClampedToFarPlane();\n' + + ' }\n' + + '}\n'; + return glsl; + } + + function getPerInstanceColorShaderFS(shaderDependencies, extentsCulling, flatShading, planarExtents) { + var glsl = + '#ifdef GL_EXT_frag_depth\n' + + '#extension GL_EXT_frag_depth : enable\n' + + '#endif\n' + + 'varying vec4 v_color;\n'; + if (extentsCulling) { + glsl += planarExtents ? + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' : + + 'varying vec4 v_sphericalExtents;\n'; + } + + glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); + + glsl += 'void main(void)\n' + + '{\n'; + + glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents); + + if (flatShading) { + glsl += + ' gl_FragColor = v_color;\n'; + } else { + glsl += + ' czm_materialInput materialInput;\n' + + ' materialInput.normalEC = normalEC;\n' + + ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n' + + ' czm_material material = czm_getDefaultMaterial(materialInput);\n' + + ' material.diffuse = v_color.rgb;\n' + + ' material.alpha = v_color.a;\n' + + + ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; + } + glsl += ' czm_writeDepthClampedToFarPlane();\n'; + glsl += '}\n'; + return glsl; + } + + function getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { + var glsl = ''; + if (shaderDependencies.requiresEC) { + glsl += + ' vec4 eyeCoordinate = getEyeCoordinate(gl_FragCoord.xy);\n'; + } + if (shaderDependencies.requiresWC) { + glsl += + ' vec4 worldCoordinate4 = czm_inverseView * eyeCoordinate;\n' + + ' vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;\n'; + } + if (shaderDependencies.requiresTextureCoordinates) { + if (planarExtents) { + glsl += + ' // Unpack planes and transform to eye space\n' + + ' float u = computePlanarTextureCoordinates(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.y);\n' + + ' float v = computePlanarTextureCoordinates(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.x);\n'; + } else { + glsl += + ' // Treat world coords as a sphere normal for spherical coordinates\n' + + ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate);\n' + + ' float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z;\n' + + ' float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w;\n'; + } + } + if (extentsCulling) { + if (!defined(outOfBoundsSnippet)) { + outOfBoundsSnippet = + ' discard;\n'; + } + glsl += + ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + + outOfBoundsSnippet + + ' }\n'; + } + // Lots of texture access, so lookup after discard check + if (shaderDependencies.requiresNormalEC) { + glsl += + ' // compute normal. sample adjacent pixels in 2x2 block in screen space\n' + + ' vec3 downUp = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + + ' vec3 leftRight = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + + ' vec3 normalEC = normalize(cross(leftRight, downUp));\n' + + '\n'; + } + return glsl; + } + + function getLocalFunctionsFS(shaderDependencies, planarExtents) { + var glsl = ''; + if (shaderDependencies.requiresEC || shaderDependencies.requiresNormalEC) { + glsl += + 'vec4 windowToEyeCoordinates(vec2 xy, float depthOrLogDepth) {\n' + + // See reverseLogDepth.glsl. This is separate to re-use the pow. + '#ifdef LOG_DEPTH\n' + + ' float near = czm_currentFrustum.x;\n' + + ' float far = czm_currentFrustum.y;\n' + + ' float unscaledDepth = pow(2.0, depthOrLogDepth * czm_log2FarPlusOne) - 1.0;\n' + + ' vec4 windowCoord = vec4(xy, far * (1.0 - near / unscaledDepth) / (far - near), 1.0);\n' + + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + ' eyeCoordinate.w = 1.0 / unscaledDepth;\n' + // Better precision + '#else\n' + + ' vec4 windowCoord = vec4(xy, depthOrLogDepth, 1.0);\n' + + ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + + '#endif\n' + + ' return eyeCoordinate;\n' + + '}\n'; + } + if (shaderDependencies.requiresEC) { + glsl += + 'vec4 getEyeCoordinate(vec2 fragCoord) {\n' + + ' vec2 coords = fragCoord / czm_viewport.zw;\n' + + ' float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + + ' return windowToEyeCoordinates(fragCoord, logDepthOrDepth);\n' + + '}\n'; + } + if (shaderDependencies.requiresNormalEC) { + glsl += + 'vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) {\n' + + ' vec4 eyeCoordinate = windowToEyeCoordinates(fragCoord, logDepthOrDepth);\n' + + ' return eyeCoordinate.xyz / eyeCoordinate.w;\n' + + '}\n' + + + 'vec3 getVectorFromOffset(vec4 eyeCoordinate, vec2 glFragCoordXY, vec2 positiveOffset) {\n' + + ' // Sample depths at both offset and negative offset\n' + + ' float upOrRightLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw));\n' + + ' float downOrLeftLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw));\n' + + ' // Explicitly evaluate both paths\n' + // Necessary for multifrustum and for GroundPrimitives at the edges of the screen + ' bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw);\n' + + ' float useUpOrRight = float(upOrRightLogDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + + ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + + ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY + positiveOffset, upOrRightLogDepth);\n' + + ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY - positiveOffset, downOrLeftLogDepth);\n' + + + ' return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft;\n' + + '}\n'; + } + if (shaderDependencies.requiresTextureCoordinates && planarExtents) { + glsl += + 'float computePlanarTextureCoordinates(vec4 plane, vec3 eyeCoordinates, float inverseExtent) {\n' + + ' return (dot(plane.xyz, eyeCoordinates) + plane.w) * inverseExtent;\n' + + '}\n'; + } + return glsl; + } + + function createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, shadowVolumeVS) { + var glsl = ShaderSource.replaceMain(shadowVolumeVS, 'computePosition'); + + var isPerInstanceColor = defined(appearance) && appearance instanceof PerInstanceColorAppearance; + if (isPerInstanceColor) { + glsl += 'varying vec4 v_color;\n'; + } + + var spherical = !(planarExtents || columbusView2D); + if (shaderDependencies.requiresTextureCoordinates) { + if (spherical) { + glsl += + 'varying vec4 v_sphericalExtents;\n' + + 'varying vec4 v_stSineCosineUVScale;\n'; + } else { + glsl += + 'varying vec2 v_inversePlaneExtents;\n' + + 'varying vec4 v_westPlane;\n' + + 'varying vec4 v_southPlane;\n' + + 'varying vec4 v_stSineCosineUVScale;\n'; + } + } + + glsl += + 'void main()\n' + + '{\n' + + ' computePosition();\n'; + if (isPerInstanceColor) { + glsl += 'v_color = czm_batchTable_color(batchId);\n'; + } + + // Add code for computing texture coordinate dependencies + if (shaderDependencies.requiresTextureCoordinates) { + if (spherical) { + glsl += + 'v_sphericalExtents = czm_batchTable_sphericalExtents(batchId);\n' + + 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; + } else { + // Two varieties of planar texcoords. 2D/CV case is "compressed" to fewer attributes + if (columbusView2D) { + glsl += + 'vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId);\n' + + 'vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId);\n' + + 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz;\n' + + 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz;\n' + + '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;\n'; + } else { + glsl += + 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz;\n' + + 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz;\n' + + 'vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz;\n'; + } + glsl += + 'vec3 eastWard = southEastCorner - southWestCorner;\n' + + 'float eastExtent = length(eastWard);\n' + + 'eastWard /= eastExtent;\n' + + + 'vec3 northWard = northWestCorner - southWestCorner;\n' + + 'float northExtent = length(northWard);\n' + + 'northWard /= northExtent;\n' + + + 'v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner));\n' + + 'v_southPlane = vec4(northWard, -dot(northWard, southWestCorner));\n' + + 'v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent);\n' + + 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; + } + } + + glsl += + '}\n'; + + return glsl; + } + + /** + * 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; + } + + ShaderDependencies.prototype.reset = function() { + this._requiresEC = false; + this._requiresWC = false; + this._requiresNormalEC = false; + this._requiresTextureCoordinates = false; + + 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; + } + } + }); + + var encodeScratch = new EncodedCartesian3(); + function addAttributesForPoint(point, name, attributes) { + var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); + + attributes[name + '_HIGH'] = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(encoded.high, [0, 0, 0]) + }); + + attributes[name + '_LOW'] = new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + normalize: false, + value : Cartesian3.pack(encoded.low, [0, 0, 0]) + }); + } + + var cartographicScratch = new Cartographic(); + var rectangleCenterScratch = new Cartographic(); + var northCenterScratch = new Cartesian3(); + var southCenterScratch = new Cartesian3(); + var eastCenterScratch = new Cartesian3(); + var westCenterScratch = new Cartesian3(); + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; + var rotation2DScratch = new Matrix2(); + var min2DScratch = new Cartesian2(); + var max2DScratch = new Cartesian2(); + function getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) { + var theta = defaultValue(textureCoordinateRotation, 0.0); + + // Compute approximate scale such that the rectangle, if scaled and rotated, + // will completely enclose the unrotated/unscaled rectangle. + var cosTheta = Math.cos(theta); + var sinTheta = Math.sin(theta); + + // Build a rectangle centered in 2D space approximating the input rectangle's dimensions + var cartoCenter = Rectangle.center(rectangle, rectangleCenterScratch); + + var carto = cartographicScratch; + carto.latitude = cartoCenter.latitude; + + carto.longitude = rectangle.west; + var westCenter = Cartographic.toCartesian(carto, ellipsoid, westCenterScratch); + + carto.longitude = rectangle.east; + var eastCenter = Cartographic.toCartesian(carto, ellipsoid, eastCenterScratch); + + carto.longitude = cartoCenter.longitude; + carto.latitude = rectangle.north; + var northCenter = Cartographic.toCartesian(carto, ellipsoid, northCenterScratch); + + carto.latitude = rectangle.south; + var southCenter = Cartographic.toCartesian(carto, ellipsoid, southCenterScratch); + + var northSouthHalfDistance = Cartesian3.distance(northCenter, southCenter) * 0.5; + var eastWestHalfDistance = Cartesian3.distance(eastCenter, westCenter) * 0.5; + + var points2D = points2DScratch; + points2D[0].x = eastWestHalfDistance; + points2D[0].y = northSouthHalfDistance; + + points2D[1].x = -eastWestHalfDistance; + points2D[1].y = northSouthHalfDistance; + + points2D[2].x = eastWestHalfDistance; + points2D[2].y = -northSouthHalfDistance; + + points2D[3].x = -eastWestHalfDistance; + points2D[3].y = -northSouthHalfDistance; + + // Rotate the dimensions rectangle and compute min/max in rotated space + var min2D = min2DScratch; + min2D.x = Number.POSITIVE_INFINITY; + min2D.y = Number.POSITIVE_INFINITY; + var max2D = max2DScratch; + max2D.x = Number.NEGATIVE_INFINITY; + max2D.y = Number.NEGATIVE_INFINITY; + + var rotation2D = Matrix2.fromRotation(-theta, rotation2DScratch); + for (var i = 0; i < 4; ++i) { + var point2D = points2D[i]; + Matrix2.multiplyByVector(rotation2D, point2D, point2D); + Cartesian2.minimumByComponent(point2D, min2D, min2D); + Cartesian2.maximumByComponent(point2D, max2D, max2D); + } + + // Depending on the rotation, east/west may be more appropriate for vertical scale than horizontal + var scaleU, scaleV; + if (Math.abs(sinTheta) < Math.abs(cosTheta)) { + scaleU = eastWestHalfDistance / ((max2D.x - min2D.x) * 0.5); + scaleV = northSouthHalfDistance / ((max2D.y - min2D.y) * 0.5); + } else { + scaleU = eastWestHalfDistance / ((max2D.y - min2D.y) * 0.5); + scaleV = northSouthHalfDistance / ((max2D.x - min2D.x) * 0.5); + } + + return new GeometryInstanceAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 4, + normalize: false, + value : [sinTheta, cosTheta, scaleU, scaleV] // Precompute trigonometry for rotation and inverse of scale + }); + } + + // Swizzle to 2D/CV projected space. This produces positions that + // can directly be used in the VS for 2D/CV, but in practice there's + // a lot of duplicated and zero values (see below). + var swizzleScratch = new Cartesian3(); + function swizzle(cartesian) { + Cartesian3.clone(cartesian, swizzleScratch); + cartesian.x = swizzleScratch.z; + cartesian.y = swizzleScratch.x; + cartesian.z = swizzleScratch.y; + return cartesian; + } + + 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) { + // Compute corner positions in double precision + var carto = cartographicScratch; + carto.height = 0.0; + + carto.longitude = rectangle.west; + carto.latitude = rectangle.south; + + var southWestCorner = swizzle(projection.project(carto, cornerScratch)); + + carto.latitude = rectangle.north; + var northWest = swizzle(projection.project(carto, northWestScratch)); + + carto.longitude = rectangle.east; + carto.latitude = rectangle.south; + var southEast = swizzle(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: y value for southWestCorner + // y: z value for southWestCorner + // z: z value for northWest + // w: y value for southEast + var valuesHigh = [0, 0, 0, 0]; + var valuesLow = [0, 0, 0, 0]; + var encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); + valuesHigh[0] = encoded.high; + valuesLow[0] = encoded.low; + + encoded = EncodedCartesian3.encode(southWestCorner.z, highLowScratch); + valuesHigh[1] = encoded.high; + valuesLow[1] = encoded.low; + + encoded = EncodedCartesian3.encode(northWest.z, highLowScratch); + valuesHigh[2] = encoded.high; + valuesLow[2] = encoded.low; + + encoded = EncodedCartesian3.encode(southEast.y, 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 + }); + } + + /** + * 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} rectangle Rectangle object that the points will approximately bound + * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. + * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. + */ + ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, projection, textureCoordinateRotation) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + Check.typeOf.object('projection', projection); + //>>includeEnd('debug'); + + // Compute corner positions in double precision + var carto = cartographicScratch; + carto.height = 0.0; + + carto.longitude = rectangle.west; + carto.latitude = rectangle.south; + + var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); + + carto.latitude = rectangle.north; + var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); + + carto.longitude = rectangle.east; + carto.latitude = rectangle.south; + var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); + + var attributes = { + stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) + }; + addAttributesForPoint(corner, 'southWest', attributes); + addAttributesForPoint(northWest, 'northWest', attributes); + addAttributesForPoint(southEast, 'southEast', attributes); + + add2DTextureCoordinateAttributes(rectangle, projection, attributes); + 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} rectangle Rectangle object that the spherical extents will approximately bound + * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates + * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. + * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. + */ + ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, projection, textureCoordinateRotation) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('ellipsoid', ellipsoid); + Check.typeOf.object('projection', projection); + //>>includeEnd('debug'); + + // rectangle cartographic coords !== spherical because it's on an ellipsoid + var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, ellipsoid, sphericalScratch); + + // Slightly pad extents to avoid floating point error when fragment culling at edges. + var south = southWestExtents.x - CesiumMath.EPSILON5; + var west = southWestExtents.y - CesiumMath.EPSILON5; + + var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, ellipsoid, sphericalScratch); + var north = northEastExtents.x + CesiumMath.EPSILON5; + var east = northEastExtents.y + 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] + }), + stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) + }; + + add2DTextureCoordinateAttributes(rectangle, projection, attributes); + return attributes; + }; + + ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function(attributes) { + return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && + defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && + defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW) && + defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && + defined(attributes.stSineCosineUVScale); + }; + + ShadowVolumeAppearance.hasAttributesForSphericalExtents = function(attributes) { + return defined(attributes.sphericalExtents) && + defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && + defined(attributes.stSineCosineUVScale); + }; + + 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); + + return ShadowVolumeAppearance; +}); diff --git a/Source/Scene/ShadowVolumeAppearanceShader.js b/Source/Scene/ShadowVolumeAppearanceShader.js deleted file mode 100644 index 4812047c9a7..00000000000 --- a/Source/Scene/ShadowVolumeAppearanceShader.js +++ /dev/null @@ -1,474 +0,0 @@ -define([ - '../Core/Check', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Renderer/PixelDatatype', - '../Renderer/ShaderSource', // TODO: where should this file live actually? - '../Scene/PerInstanceColorAppearance' -], function( - Check, - defaultValue, - defined, - defineProperties, - PixelDatatype, - ShaderSource, - PerInstanceColorAppearance) { - 'use strict'; - - /** - * Creates the shadow volume fragment shader for a ClassificationPrimitive to use a given appearance. - * - * @param {Boolean} extentsCulling Discard fragments outside the instance's spherical extents. - * @param {Boolean} planarExtents - * @param {Boolean} columbusView2D - * @param {Appearance} [appearance] An Appearance to be used with a ClassificationPrimitive. Leave undefined for picking. - * @returns {String} Shader source for a fragment shader using the input appearance. - * @private - */ - function ShadowVolumeAppearanceShader(extentsCulling, planarExtents, columbusView2D, vertexShader, appearance) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.bool('extentsCulling', extentsCulling); - Check.typeOf.bool('planarExtents', planarExtents); - Check.typeOf.bool('columbusView2D', columbusView2D); - Check.typeOf.string('vertexShader', vertexShader); - //>>includeEnd('debug'); - - var shaderDependencies = new ShaderDependencies(); - this._shaderDependencies = shaderDependencies; - this._extentsCulling = extentsCulling; - this._planarExtents = planarExtents || columbusView2D; - this._fragmenShaderSource = createShadowVolumeAppearanceFS(shaderDependencies, appearance, this._extentsCulling, this._planarExtents); - this._vertexShaderSource = createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, vertexShader); - } - - defineProperties(ShadowVolumeAppearanceShader.prototype, { - /** - * Whether or not the resulting shader's texture coordinates are computed from planar extents. - * - * @memberof ShadowVolumeAppearanceShader.prototype - * @type {Boolean} - * @readonly - */ - planarExtents : { - get : function() { - return this._planarExtents; - } - }, - /** - * The fragment shader source. - * @memberof ShadowVolumeAppearanceShader.prototype - * @type {String} - * @readonly - */ - fragmentShaderSource : { - get : function() { - return this._fragmenShaderSource; - } - }, - /** - * The vertex shader source. - * @memberof ShadowVolumeAppearanceShader.prototype - * @type {String} - * @readonly - */ - vertexShaderSource : { - get : function() { - return this._vertexShaderSource; - } - } - }); - - function createShadowVolumeAppearanceFS(shaderDependencies, appearance, extentsCull, planarExtents) { - if (!defined(appearance)) { - return getColorlessShaderFS(shaderDependencies, extentsCull, planarExtents); - } - if (appearance instanceof PerInstanceColorAppearance) { - return getPerInstanceColorShaderFS(shaderDependencies, extentsCull, appearance.flat, planarExtents); - } - - shaderDependencies.requiresTextureCoordinates = extentsCull; - shaderDependencies.requiresEC = !appearance.flat; - - // Scan material source for what hookups are needed. Assume czm_materialInput materialInput. - var materialShaderSource = appearance.material.shaderSource; - - var usesNormalEC = shaderDependencies.normalEC = materialShaderSource.includes('materialInput.normalEC') || materialShaderSource.includes('czm_getDefaultMaterial'); - var usesPositionToEyeEC = shaderDependencies.positionToEyeEC = materialShaderSource.includes('materialInput.positionToEyeEC'); - var usesTangentToEyeMat = shaderDependencies.tangentToEyeMatrix = materialShaderSource.includes('materialInput.tangentToEyeMatrix'); - var usesSt = shaderDependencies.st = materialShaderSource.includes('materialInput.st'); - - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n'; - if (extentsCull || usesSt) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; - } - if (usesSt) { - glsl += - 'varying vec4 v_stSineCosineUVScale;\n'; - } - - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); - - glsl += - 'void main(void)\n' + - '{\n'; - - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCull, planarExtents); - - glsl += ' czm_materialInput materialInput;\n'; - if (usesNormalEC) { - glsl += ' materialInput.normalEC = normalEC;\n'; - } - if (usesPositionToEyeEC) { - glsl += ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n'; - } - if (usesTangentToEyeMat) { - glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC);\n'; - } - if (usesSt) { - // Scale texture coordinates and rotate around 0.5, 0.5 - glsl += - ' materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5;\n' + - ' materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5;\n'; - } - glsl += ' czm_material material = czm_getMaterial(materialInput);\n'; - - if (appearance.flat) { - glsl += ' gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n'; - } else { - glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; - } - glsl += ' czm_writeDepthClampedToFarPlane();\n'; - glsl += '}\n'; - return glsl; - } - - function getColorlessShaderFS(shaderDependencies, extentsCulling, planarExtents) { - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n'; - if (extentsCulling) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; - } - shaderDependencies.requiresTextureCoordinates = extentsCulling; - shaderDependencies.requiresNormalEC = false; - - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); - - glsl += 'void main(void)\n' + - '{\n'; - glsl += ' bool culled = false;\n'; - var outOfBoundsSnippet = - ' culled = true;\n'; - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); - glsl += ' if (!culled) {\n' + - ' gl_FragColor.a = 1.0;\n' + // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource - ' czm_writeDepthClampedToFarPlane();\n' + - ' }\n' + - '}\n'; - return glsl; - } - - function getPerInstanceColorShaderFS(shaderDependencies, extentsCulling, flatShading, planarExtents) { - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n' + - 'varying vec4 v_color;\n'; - if (extentsCulling) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; - } - shaderDependencies.requiresTextureCoordinates = extentsCulling; - shaderDependencies.requiresNormalEC = !flatShading; - - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); - - glsl += 'void main(void)\n' + - '{\n'; - - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents); - - if (flatShading) { - glsl += - ' gl_FragColor = v_color;\n'; - } else { - glsl += - ' czm_materialInput materialInput;\n' + - ' materialInput.normalEC = normalEC;\n' + - ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n' + - ' czm_material material = czm_getDefaultMaterial(materialInput);\n' + - ' material.diffuse = v_color.rgb;\n' + - ' material.alpha = v_color.a;\n' + - - ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; - } - glsl += ' czm_writeDepthClampedToFarPlane();\n'; - glsl += '}\n'; - return glsl; - } - - function getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { - var glsl = ''; - if (shaderDependencies.requiresEC) { - glsl += - ' vec4 eyeCoordinate = getEyeCoordinate(gl_FragCoord.xy);\n'; - } - if (shaderDependencies.requiresWC) { - glsl += - ' vec4 worldCoordinate4 = czm_inverseView * eyeCoordinate;\n' + - ' vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;\n'; - } - if (shaderDependencies.requiresTextureCoordinates) { - if (planarExtents) { - glsl += - ' // Unpack planes and transform to eye space\n' + - ' float u = computePlanarTextureCoordinates(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.y);\n' + - ' float v = computePlanarTextureCoordinates(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.x);\n'; - } else { - glsl += - ' // Treat world coords as a sphere normal for spherical coordinates\n' + - ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate);\n' + - ' float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z;\n' + - ' float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w;\n'; - } - } - if (extentsCulling) { - if (!defined(outOfBoundsSnippet)) { - outOfBoundsSnippet = - ' discard;\n'; - } - glsl += - ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + - outOfBoundsSnippet + - ' }\n'; - } - // Lots of texture access, so lookup after discard check - if (shaderDependencies.requiresNormalEC) { - glsl += - ' // compute normal. sample adjacent pixels in 2x2 block in screen space\n' + - ' vec3 downUp = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + - ' vec3 leftRight = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + - ' vec3 normalEC = normalize(cross(leftRight, downUp));\n' + - '\n'; - } - return glsl; - } - - function getLocalFunctionsFS(shaderDependencies, planarExtents) { - var glsl = ''; - if (shaderDependencies.requiresEC) { - glsl += - 'vec4 getEyeCoordinate(vec2 fragCoord) {\n' + - ' vec2 coords = fragCoord / czm_viewport.zw;\n' + - ' float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + - ' vec4 windowCoord = vec4(fragCoord, czm_reverseLogDepth(logDepthOrDepth), 1.0);\n' + - ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + - '#ifdef LOG_DEPTH\n' + - // Essentially same as reverseLogDepth but without normalization. Better precision when using log depth. - ' eyeCoordinate.w = 1.0 / (pow(2.0, logDepthOrDepth * czm_log2FarPlusOne) - 1.0);\n' + - '#endif\n' + - ' return eyeCoordinate;\n' + - '}\n'; - } - if (shaderDependencies.requiresNormalEC) { - glsl += - 'vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) {\n' + - ' vec4 windowCoord = vec4(fragCoord, czm_reverseLogDepth(logDepthOrDepth), 1.0);\n' + - ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + - '#ifdef LOG_DEPTH\n' + - // Essentially same as reverseLogDepth but without normalization. Better precision when using log depth. - ' eyeCoordinate.w = 1.0 / (pow(2.0, logDepthOrDepth * czm_log2FarPlusOne) - 1.0);\n' + - '#endif\n' + - ' return eyeCoordinate.xyz / eyeCoordinate.w;\n' + - '}\n' + - - 'vec3 getVectorFromOffset(vec4 eyeCoordinate, vec2 glFragCoordXY, vec2 positiveOffset) {\n' + - ' // Sample depths at both offset and negative offset\n' + - ' float upOrRightLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw));\n' + - ' float downOrLeftLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw));\n' + - ' // Explicitly evaluate both paths\n' + // Necessary for multifrustum and for GroundPrimitives at the edges of the screen - ' bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw);\n' + - ' float useUpOrRight = float(upOrRightLogDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + - ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + - ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY + positiveOffset, upOrRightLogDepth);\n' + - ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY - positiveOffset, downOrLeftLogDepth);\n' + - - ' return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft;\n' + - '}\n'; - } - if (shaderDependencies.requiresTextureCoordinates && planarExtents) { - glsl += - 'float computePlanarTextureCoordinates(vec4 plane, vec3 eyeCoordinates, float inverseExtent) {\n' + - ' return (dot(plane.xyz, eyeCoordinates) + plane.w) * inverseExtent;\n' + - '}\n'; - } - return glsl; - } - - function createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, shadowVolumeVS) { - var glsl = ShaderSource.replaceMain(shadowVolumeVS, 'computePosition'); - - var isPerInstanceColor = defined(appearance) && appearance instanceof PerInstanceColorAppearance; - if (isPerInstanceColor) { - glsl += 'varying vec4 v_color;\n'; - } - - var spherical = !(planarExtents || columbusView2D); - if (shaderDependencies.requiresTextureCoordinates) { - if (spherical) { - glsl += - 'varying vec4 v_sphericalExtents;\n' + - 'varying vec4 v_stSineCosineUVScale;\n'; - } else { - glsl += - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' + - 'varying vec4 v_stSineCosineUVScale;\n'; - } - } - - glsl += - 'void main()\n' + - '{\n' + - ' computePosition();\n'; - if (isPerInstanceColor) { - glsl += 'v_color = czm_batchTable_color(batchId);\n'; - } - - // Add code for computing texture coordinate dependencies - if (shaderDependencies.requiresTextureCoordinates) { - if (spherical) { - glsl += - 'v_sphericalExtents = czm_batchTable_sphericalExtents(batchId);\n' + - 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; - } else { - // Two varieties of planar texcoords. 2D/CV case is "compressed" - if (columbusView2D) { - glsl += - 'vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId);\n' + - 'vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId);\n' + - 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz;\n' + - 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz;\n' + - '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;\n'; - } else { - glsl += - 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz;\n' + - 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz;\n' + - 'vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz;\n'; - } - glsl += - 'vec3 eastWard = southEastCorner - southWestCorner;\n' + - 'float eastExtent = length(eastWard);\n' + - 'eastWard /= eastExtent;\n' + - - 'vec3 northWard = northWestCorner - southWestCorner;\n' + - 'float northExtent = length(northWard);\n' + - 'northWard /= northExtent;\n' + - - 'v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner));\n' + - 'v_southPlane = vec4(northWard, -dot(northWard, southWestCorner));\n' + - 'v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent);\n' + - 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; - } - } - - glsl += - '}\n'; - - return glsl; - } - - /** - * 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 - } - - 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; - } - }, - // Set when assessing material hookups - normalEC : { - set : function(value) { - this.requiresNormalEC = value; - } - }, - tangentToEyeMatrix : { - set : function(value) { - this.requiresWC = value; - this.requiresNormalEC = value; - } - }, - positionToEyeEC : { - set : function(value) { - this.requiresEC = value; - } - }, - st : { - set : function(value) { - this.requiresTextureCoordinates = value; - } - } - }); - - return ShadowVolumeAppearanceShader; -}); From d9ed5c56cf01ceaf08167432aca09ff8f00ca20a Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 26 Apr 2018 23:40:51 -0400 Subject: [PATCH 23/40] unit tests --- Source/DataSources/GeometryVisualizer.js | 2 +- .../StaticGroundGeometryColorBatch.js | 356 ------------------ .../StaticGroundGeometryPerMaterialBatch.js | 66 +--- Source/Scene/ClassificationPrimitive.js | 114 +++--- Source/Scene/Scene.js | 2 + Source/Scene/ShadowVolumeAppearance.js | 36 +- Specs/Core/RectangleCollisionCheckerSpec.js | 35 ++ Specs/DataSources/GeometryVisualizerSpec.js | 6 +- Specs/DataSources/PolylineVisualizerSpec.js | 2 - .../StaticGroundGeometryColorBatchSpec.js | 239 ------------ ...taticGroundGeometryPerMaterialBatchSpec.js | 306 +++++++++++++++ Specs/Scene/ClassificationPrimitiveSpec.js | 80 ++-- Specs/Scene/GroundPrimitiveSpec.js | 193 +++++++--- Specs/Scene/SceneSpec.js | 1 + Specs/Scene/ShadowVolumeAppearanceSpec.js | 297 +++++++++++++++ ...reateGeometryUpdaterGroundGeometrySpecs.js | 24 +- 16 files changed, 937 insertions(+), 822 deletions(-) delete mode 100644 Source/DataSources/StaticGroundGeometryColorBatch.js create mode 100644 Specs/Core/RectangleCollisionCheckerSpec.js delete mode 100644 Specs/DataSources/StaticGroundGeometryColorBatchSpec.js create mode 100644 Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js create mode 100644 Specs/Scene/ShadowVolumeAppearanceSpec.js diff --git a/Source/DataSources/GeometryVisualizer.js b/Source/DataSources/GeometryVisualizer.js index 738b85bfd03..be777ad5bee 100644 --- a/Source/DataSources/GeometryVisualizer.js +++ b/Source/DataSources/GeometryVisualizer.js @@ -156,7 +156,7 @@ define([ var numberOfClassificationTypes = ClassificationType.NUMBER_OF_CLASSIFICATION_TYPES; this._groundColorBatches = new Array(numberOfClassificationTypes); - this._groundMaterialBatches = new Array(numberOfClassificationTypes); // TODO: why is this? + this._groundMaterialBatches = new Array(numberOfClassificationTypes); for (i = 0; i < numberOfClassificationTypes; ++i) { this._groundColorBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance, i); diff --git a/Source/DataSources/StaticGroundGeometryColorBatch.js b/Source/DataSources/StaticGroundGeometryColorBatch.js deleted file mode 100644 index 9dcfc20bfde..00000000000 --- a/Source/DataSources/StaticGroundGeometryColorBatch.js +++ /dev/null @@ -1,356 +0,0 @@ -define([ - '../Core/AssociativeArray', - '../Core/Color', - '../Core/defined', - '../Core/DistanceDisplayCondition', - '../Core/DistanceDisplayConditionGeometryInstanceAttribute', - '../Core/ShowGeometryInstanceAttribute', - '../Scene/GroundPrimitive', - './BoundingSphereState', - './Property' - ], function( - AssociativeArray, - Color, - defined, - DistanceDisplayCondition, - DistanceDisplayConditionGeometryInstanceAttribute, - ShowGeometryInstanceAttribute, - GroundPrimitive, - BoundingSphereState, - Property) { - 'use strict'; - - var colorScratch = new Color(); - var distanceDisplayConditionScratch = new DistanceDisplayCondition(); - var defaultDistanceDisplayCondition = new DistanceDisplayCondition(); - - function Batch(primitives, classificationType, color, key) { - this.primitives = primitives; - this.classificationType = classificationType; - this.color = color; - this.key = key; - this.createPrimitive = false; - this.waitingOnCreate = false; - this.primitive = undefined; - this.oldPrimitive = undefined; - this.geometry = new AssociativeArray(); - this.updaters = new AssociativeArray(); - this.updatersWithAttributes = new AssociativeArray(); - this.attributes = new AssociativeArray(); - this.subscriptions = new AssociativeArray(); - this.showsUpdated = new AssociativeArray(); - this.itemsToRemove = []; - this.isDirty = false; - } - - Batch.prototype.add = function(updater, instance) { - var id = updater.id; - this.createPrimitive = true; - this.geometry.set(id, instance); - this.updaters.set(id, updater); - if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { - this.updatersWithAttributes.set(id, updater); - } else { - var that = this; - this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { - if (propertyName === 'isShowing') { - that.showsUpdated.set(updater.id, updater); - } - })); - } - }; - - Batch.prototype.remove = function(updater) { - var id = updater.id; - this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; - if (this.updaters.remove(id)) { - this.updatersWithAttributes.remove(id); - var unsubscribe = this.subscriptions.get(id); - if (defined(unsubscribe)) { - unsubscribe(); - this.subscriptions.remove(id); - } - } - }; - - var scratchArray = new Array(4); - - Batch.prototype.update = function(time) { - var isUpdated = true; - var removedCount = 0; - var primitive = this.primitive; - var primitives = this.primitives; - var attributes; - var i; - - if (this.createPrimitive) { - var geometries = this.geometry.values; - var geometriesLength = geometries.length; - if (geometriesLength > 0) { - if (defined(primitive)) { - if (!defined(this.oldPrimitive)) { - this.oldPrimitive = primitive; - } else { - primitives.remove(primitive); - } - } - - for (i = 0; i < geometriesLength; i++) { - var geometryItem = geometries[i]; - var originalAttributes = geometryItem.attributes; - attributes = this.attributes.get(geometryItem.id.id); - - if (defined(attributes)) { - if (defined(originalAttributes.show)) { - originalAttributes.show.value = attributes.show; - } - if (defined(originalAttributes.color)) { - originalAttributes.color.value = attributes.color; - } - } - } - - primitive = new GroundPrimitive({ - show : false, - asynchronous : true, - geometryInstances : geometries, - classificationType : this.classificationType - }); - primitives.add(primitive); - isUpdated = false; - } else { - if (defined(primitive)) { - primitives.remove(primitive); - primitive = undefined; - } - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; - } - } - - this.attributes.removeAll(); - this.primitive = primitive; - this.createPrimitive = false; - this.waitingOnCreate = true; - } else if (defined(primitive) && primitive.ready) { - primitive.show = true; - if (defined(this.oldPrimitive)) { - primitives.remove(this.oldPrimitive); - this.oldPrimitive = undefined; - } - var updatersWithAttributes = this.updatersWithAttributes.values; - var length = updatersWithAttributes.length; - var waitingOnCreate = this.waitingOnCreate; - for (i = 0; i < length; i++) { - var updater = updatersWithAttributes[i]; - var instance = this.geometry.get(updater.id); - - attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } - - if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { - var colorProperty = updater.fillMaterialProperty.color; - var fillColor = Property.getValueOrDefault(colorProperty, time, Color.WHITE, colorScratch); - - if (!Color.equals(attributes._lastColor, fillColor)) { - attributes._lastColor = Color.clone(fillColor, attributes._lastColor); - var color = this.color; - var newColor = fillColor.toBytes(scratchArray); - if (color[0] !== newColor[0] || color[1] !== newColor[1] || - color[2] !== newColor[2] || color[3] !== newColor[3]) { - this.itemsToRemove[removedCount++] = updater; - } - } - } - - var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); - } - - var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; - if (!Property.isConstant(distanceDisplayConditionProperty)) { - var distanceDisplayCondition = Property.getValueOrDefault(distanceDisplayConditionProperty, time, defaultDistanceDisplayCondition, distanceDisplayConditionScratch); - if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { - attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); - attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); - } - } - } - - this.updateShows(primitive); - this.waitingOnCreate = false; - } else if (defined(primitive) && !primitive.ready) { - isUpdated = false; - } - this.itemsToRemove.length = removedCount; - return isUpdated; - }; - - Batch.prototype.updateShows = function(primitive) { - var showsUpdated = this.showsUpdated.values; - var length = showsUpdated.length; - for (var i = 0; i < length; i++) { - var updater = showsUpdated[i]; - var instance = this.geometry.get(updater.id); - - var attributes = this.attributes.get(instance.id.id); - if (!defined(attributes)) { - attributes = primitive.getGeometryInstanceAttributes(instance.id); - this.attributes.set(instance.id.id, attributes); - } - - var show = updater.entity.isShowing; - var currentShow = attributes.show[0] === 1; - if (show !== currentShow) { - attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); - } - } - this.showsUpdated.removeAll(); - }; - - Batch.prototype.contains = function(updater) { - return this.updaters.contains(updater.id); - }; - - Batch.prototype.getBoundingSphere = function(updater, result) { - var primitive = this.primitive; - if (!primitive.ready) { - return BoundingSphereState.PENDING; - } - - var bs = primitive.getBoundingSphere(updater.entity); - if (!defined(bs)) { - return BoundingSphereState.FAILED; - } - - bs.clone(result); - return BoundingSphereState.DONE; - }; - - Batch.prototype.removeAllPrimitives = function() { - var primitives = this.primitives; - - var primitive = this.primitive; - if (defined(primitive)) { - primitives.remove(primitive); - this.primitive = undefined; - this.geometry.removeAll(); - this.updaters.removeAll(); - } - - var oldPrimitive = this.oldPrimitive; - if (defined(oldPrimitive)) { - primitives.remove(oldPrimitive); - this.oldPrimitive = undefined; - } - }; - - /** - * @private - */ - function StaticGroundGeometryColorBatch(primitives, classificationType) { - this._batches = new AssociativeArray(); - this._primitives = primitives; - this._classificationType = classificationType; - } - - StaticGroundGeometryColorBatch.prototype.add = function(time, updater) { - var instance = updater.createFillGeometryInstance(time); - var batches = this._batches; - // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key - var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0]; - var batch; - if (batches.contains(batchKey)) { - batch = batches.get(batchKey); - } else { - batch = new Batch(this._primitives, this._classificationType, instance.attributes.color.value, batchKey); - batches.set(batchKey, batch); - } - batch.add(updater, instance); - return batch; - }; - - StaticGroundGeometryColorBatch.prototype.remove = function(updater) { - var batchesArray = this._batches.values; - var count = batchesArray.length; - for (var i = 0; i < count; ++i) { - if (batchesArray[i].remove(updater)) { - return; - } - } - }; - - StaticGroundGeometryColorBatch.prototype.update = function(time) { - var i; - var updater; - - //Perform initial update - var isUpdated = true; - var batches = this._batches; - var batchesArray = batches.values; - var batchCount = batchesArray.length; - for (i = 0; i < batchCount; ++i) { - isUpdated = batchesArray[i].update(time) && isUpdated; - } - - //If any items swapped between batches we need to move them - for (i = 0; i < batchCount; ++i) { - var oldBatch = batchesArray[i]; - var itemsToRemove = oldBatch.itemsToRemove; - var itemsToMoveLength = itemsToRemove.length; - for (var j = 0; j < itemsToMoveLength; j++) { - updater = itemsToRemove[j]; - oldBatch.remove(updater); - var newBatch = this.add(time, updater); - oldBatch.isDirty = true; - newBatch.isDirty = true; - } - } - - //If we moved anything around, we need to re-build the primitive and remove empty batches - var batchesArrayCopy = batchesArray.slice(); - var batchesCopyCount = batchesArrayCopy.length; - for (i = 0; i < batchesCopyCount; ++i) { - var batch = batchesArrayCopy[i]; - if (batch.isDirty) { - isUpdated = batchesArrayCopy[i].update(time) && isUpdated; - batch.isDirty = false; - } - if (batch.geometry.length === 0) { - batches.remove(batch.key); - } - } - - return isUpdated; - }; - - StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(updater, result) { - var batchesArray = this._batches.values; - var batchCount = batchesArray.length; - for (var i = 0; i < batchCount; ++i) { - var batch = batchesArray[i]; - if (batch.contains(updater)) { - return batch.getBoundingSphere(updater, result); - } - } - - return BoundingSphereState.FAILED; - }; - - StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { - var batchesArray = this._batches.values; - var batchCount = batchesArray.length; - for (var i = 0; i < batchCount; ++i) { - batchesArray[i].removeAllPrimitives(); - } - }; - - return StaticGroundGeometryColorBatch; -}); diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index d7e83022652..1cc9a724eef 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -1,6 +1,5 @@ define([ '../Core/AssociativeArray', - '../Core/Color', '../Core/ColorGeometryInstanceAttribute', '../Core/defined', '../Core/DistanceDisplayCondition', @@ -15,7 +14,6 @@ define([ './Property' ], function( AssociativeArray, - Color, ColorGeometryInstanceAttribute, defined, DistanceDisplayCondition, @@ -31,11 +29,13 @@ define([ 'use strict'; var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + var defaultDistanceDisplayCondition = new DistanceDisplayCondition(); // Encapsulates a Primitive and all the entities that it represents. - function Batch(primitives, appearanceType, materialProperty, shadows, usingSphericalCoordinates) { + function Batch(primitives, appearanceType, classificationType, materialProperty, usingSphericalTextureCoordinates) { this.primitives = primitives; // scene level primitive collection this.appearanceType = appearanceType; + this.classificationType = classificationType; this.materialProperty = materialProperty; this.updaters = new AssociativeArray(); this.createPrimitive = true; @@ -49,8 +49,7 @@ define([ this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); this.subscriptions = new AssociativeArray(); this.showsUpdated = new AssociativeArray(); - this.shadows = shadows; - this.usingSphericalCoordinates = usingSphericalCoordinates; + this.usingSphericalTextureCoordinates = usingSphericalTextureCoordinates; this.rectangleCollisionCheck = new RectangleCollisionChecker(); } @@ -62,6 +61,7 @@ define([ return !this.rectangleCollisionCheck.collides(rectangle); }; + // Check if the given updater's material is compatible with this batch Batch.prototype.isMaterial = function(updater) { var material = this.materialProperty; var updaterMaterial = updater.fillMaterialProperty; @@ -78,10 +78,12 @@ define([ this.updaters.set(id, updater); this.geometry.set(id, geometryInstance); this.rectangleCollisionCheck.insert(id, geometryInstance.geometry.rectangle); + // Updaters with dynamic attributes must be tracked separately, may exit the batch if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { this.updatersWithAttributes.set(id, updater); } else { var that = this; + // Listen for show changes. These will be synchronized in updateShows. this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { if (propertyName === 'isShowing') { that.showsUpdated.set(updater.id, updater); @@ -107,8 +109,6 @@ define([ return this.createPrimitive; }; - var colorScratch = new Color(); - Batch.prototype.update = function(time) { var isUpdated = true; var primitive = this.primitive; @@ -121,9 +121,11 @@ define([ var geometriesLength = geometries.length; if (geometriesLength > 0) { if (defined(primitive)) { + // Keep a handle to the old primitive so it can be removed when the updated version is ready. if (!defined(this.oldPrimitive)) { this.oldPrimitive = primitive; } else { + // For if the new primitive changes again before it is ready. primitives.remove(primitive); } } @@ -140,31 +142,13 @@ define([ if (defined(originalAttributes.color)) { originalAttributes.color.value = attributes.color; } - if (defined(originalAttributes.depthFailColor)) { - originalAttributes.depthFailColor.value = attributes.depthFailColor; - } } } this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); - var depthFailAppearance; - if (defined(this.depthFailMaterialProperty)) { - var translucent; - if (this.depthFailMaterialProperty instanceof MaterialProperty) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - translucent = this.depthFailMaterial.isTranslucent(); - } else { - translucent = this.material.isTranslucent(); - } - depthFailAppearance = new this.depthFailAppearanceType({ - material : this.depthFailMaterial, - translucent : translucent, - closed : this.closed - }); - } - primitive = new GroundPrimitive({ + show : false, asynchronous : true, geometryInstances : geometries, appearance : new this.appearanceType({ @@ -172,8 +156,7 @@ define([ translucent : this.material.isTranslucent(), closed : this.closed }), - depthFailAppearance : depthFailAppearance, - shadows : this.shadows + classificationType : this.classificationType }); primitives.add(primitive); @@ -194,6 +177,7 @@ define([ this.primitive = primitive; this.createPrimitive = false; } else if (defined(primitive) && primitive.ready) { + primitive.show = true; if (defined(this.oldPrimitive)) { primitives.remove(this.oldPrimitive); this.oldPrimitive = undefined; @@ -202,11 +186,6 @@ define([ this.material = MaterialProperty.getValue(time, this.materialProperty, this.material); this.primitive.appearance.material = this.material; - if (defined(this.depthFailAppearanceType) && !(this.depthFailMaterialProperty instanceof ColorMaterialProperty)) { - this.depthFailMaterial = MaterialProperty.getValue(time, this.depthFailMaterialProperty, this.depthFailMaterial); - this.primitive.depthFailAppearance.material = this.depthFailMaterial; - } - var updatersWithAttributes = this.updatersWithAttributes.values; var length = updatersWithAttributes.length; for (i = 0; i < length; i++) { @@ -220,15 +199,6 @@ define([ this.attributes.set(instance.id.id, attributes); } - if (defined(this.depthFailAppearanceType) && this.depthFailAppearanceType instanceof ColorMaterialProperty && !updater.depthFailMaterialProperty.isConstant) { - var depthFailColorProperty = updater.depthFailMaterialProperty.color; - depthFailColorProperty.getValue(time, colorScratch); - if (!Color.equals(attributes._lastDepthFailColor, colorScratch)) { - attributes._lastDepthFailColor = Color.clone(colorScratch, attributes._lastDepthFailColor); - attributes.depthFailColor = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.depthFailColor); - } - } - var show = entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); var currentShow = attributes.show[0] === 1; if (show !== currentShow) { @@ -237,7 +207,7 @@ define([ var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; if (!Property.isConstant(distanceDisplayConditionProperty)) { - var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch); + var distanceDisplayCondition = Property.getValueOrDefault(distanceDisplayConditionProperty, time, defaultDistanceDisplayCondition, distanceDisplayConditionScratch); if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); @@ -309,28 +279,28 @@ define([ /** * @private */ - function StaticGroundGeometryPerMaterialBatch(primitives, appearanceType, shadows) { + function StaticGroundGeometryPerMaterialBatch(primitives, appearanceType, classificationType) { this._items = []; this._primitives = primitives; this._appearanceType = appearanceType; - this._shadows = shadows; + this._classificationType = classificationType; } StaticGroundGeometryPerMaterialBatch.prototype.add = function(time, updater) { var items = this._items; var length = items.length; var geometryInstance = updater.createFillGeometryInstance(time); - var usingSphericalCoordinates = ShadowVolumeAppearance.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); + var usingSphericalTextureCoordinates = ShadowVolumeAppearance.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); for (var i = 0; i < length; ++i) { var item = items[i]; if (item.isMaterial(updater) && item.nonOverlapping(geometryInstance.geometry.rectangle) && - item.usingSphericalCoordinates === usingSphericalCoordinates) { + item.usingSphericalTextureCoordinates === usingSphericalTextureCoordinates) { item.add(time, updater, geometryInstance); return; } } - var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._shadows, usingSphericalCoordinates); + var batch = new Batch(this._primitives, this._appearanceType, this._classificationType, updater.fillMaterialProperty, usingSphericalTextureCoordinates); batch.add(time, updater, geometryInstance); items.push(batch); }; diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 450cb78bfb8..6c0656321b0 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -190,56 +190,12 @@ define([ this._primitive = undefined; this._pickPrimitive = options._pickPrimitive; - var appearance = options.appearance; + // Set in update + this._hasSphericalExtentsAttribute = false; + this._hasPlanarExtentsAttributes = false; + this._hasPerColorAttribute = false; - var hasPerColorAttribute = false; - var hasSphericalExtentsAttribute = false; - var hasPlanarExtentsAttributes = false; - - var geometryInstancesArray = isArray(geometryInstances) ? geometryInstances : [geometryInstances]; - var geometryInstanceCount = geometryInstancesArray.length; - for (var i = 0; i < geometryInstanceCount; i++) { - var attributes = geometryInstancesArray[i].attributes; - if (defined(attributes)) { - if (defined(attributes.color)) { - hasPerColorAttribute = true; - } else if (hasPerColorAttribute) { - throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } - if (ShadowVolumeAppearance.hasAttributesForSphericalExtents) { - hasSphericalExtentsAttribute = true; - } else if (hasSphericalExtentsAttribute) { - throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } - if (ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes)) { - hasPlanarExtentsAttributes = true; - } else if (hasPlanarExtentsAttributes) { - throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } - } else if (hasPerColorAttribute || hasSphericalExtentsAttribute) { - throw new DeveloperError('All GeometryInstances must have the same attributes.'); - } - } - - // If attributes include color and appearance is undefined, default to a color appearance - if (!defined(appearance) && hasPerColorAttribute) { - appearance = new PerInstanceColorAppearance({ - flat : true - }); - } - - if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { - throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); - } - - if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { - throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute or GeometryInstanceAttributes for computing planes'); - } - - this._hasPerColorAttribute = hasPerColorAttribute; - this._hasSphericalExtentsAttribute = hasSphericalExtentsAttribute; - this._hasPlanarExtentsAttributes = hasPlanarExtentsAttributes; - this.appearance = appearance; + this.appearance = options.appearance; var readOnlyAttributes; if (defined(geometryInstances) && isArray(geometryInstances) && geometryInstances.length > 1) { @@ -251,7 +207,7 @@ define([ this._primitiveOptions = { geometryInstances : undefined, - appearance : appearance, + appearance : undefined, vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), interleave : defaultValue(options.interleave, false), releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), @@ -964,21 +920,54 @@ define([ var i; var instance; - //>>includeStart('debug', pragmas.debug); - var color; - for (i = 0; i < length; ++i) { + + var hasPerColorAttribute = false; + var hasSphericalExtentsAttribute = false; + var hasPlanarExtentsAttributes = false; + + for (i = 0; i < length; i++) { instance = instances[i]; var attributes = instance.attributes; - if (!defined(attributes) || !defined(attributes.color)) { - //throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); - } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) { - //throw new DeveloperError('Not all of the geometry instances have the same color attribute.'); - } else if (!defined(color)) { - color = attributes.color; + if (defined(attributes.color)) { + hasPerColorAttribute = true; + } + //>>includeStart('debug', pragmas.debug); + else if (hasPerColorAttribute) { + throw new DeveloperError('All GeometryInstances must have the same attributes.'); + } + //>>includeEnd('debug'); + + // Not expecting these to be set by users, should only be set via GroundPrimitive. + // So don't check for mismatch. + if (ShadowVolumeAppearance.hasAttributesForSphericalExtents(attributes)) { + hasSphericalExtentsAttribute = true; + } + if (ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes)) { + hasPlanarExtentsAttributes = true; } } + + // default to a color appearance + if (hasPerColorAttribute && !defined(appearance)) { + appearance = new PerInstanceColorAppearance({ + flat : true + }); + this.appearance = appearance; + } + + //>>includeStart('debug', pragmas.debug); + if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { + throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); + } + if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { + throw new DeveloperError('Materials on ClassificationPrimitives are not supported except via GroundPrimitive'); + } //>>includeEnd('debug'); + this._hasSphericalExtentsAttribute = hasSphericalExtentsAttribute; + this._hasPlanarExtentsAttributes = hasPlanarExtentsAttributes; + this._hasPerColorAttribute = hasPerColorAttribute; + var geometryInstances = new Array(length); for (i = 0; i < length; ++i) { instance = instances[i]; @@ -991,6 +980,7 @@ define([ }); } + primitiveOptions.appearance = appearance; primitiveOptions.geometryInstances = geometryInstances; if (defined(this._createBoundingVolumeFunction)) { @@ -1049,14 +1039,16 @@ define([ } // Update primitive appearance if (this._primitive.appearance !== appearance) { + //>>includeStart('debug', pragmas.debug); // Check if the appearance is supported by the geometry attributes if (!this._hasSphericalExtentsAttribute && !this._hasPlanarExtentsAttributes && defined(appearance.material)) { - throw new DeveloperError('Materials on ClassificationPrimitives requires sphericalExtents GeometryInstanceAttribute or GeometryInstanceAttributes for computing planes'); + throw new DeveloperError('Materials on ClassificationPrimitives are not supported except via GroundPrimitive'); } if (!this._hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); } - this._primitive.appearance = appearance; + //>>includeEnd('debug'); + this._primitive.appearance = appearance; } this._primitive.show = this.show; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 9f836fb839f..776143d5827 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -646,6 +646,8 @@ define([ /** * Set to true to copy the depth texture after rendering the globe. Makes czm_globeDepthTexture valid. + * Set to false if Entities on terrain or GroundPrimitives are not used for a potential performance improvement. + * * @type {Boolean} * @default true * @private diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index d2ab636160d..6bdd807e143 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -57,7 +57,7 @@ define([ shaderDependencies.requiresNormalEC = !appearance.flat; } else { // Scan material source for what hookups are needed. Assume czm_materialInput materialInput. - var materialShaderSource = appearance.material.shaderSource; + var materialShaderSource = appearance.material.shaderSource + '\n' + appearance.fragmentShaderSource; shaderDependencies.normalEC = materialShaderSource.includes('materialInput.normalEC') || materialShaderSource.includes('czm_getDefaultMaterial'); shaderDependencies.positionToEyeEC = materialShaderSource.includes('materialInput.positionToEyeEC'); @@ -651,18 +651,6 @@ define([ }); } - // Swizzle to 2D/CV projected space. This produces positions that - // can directly be used in the VS for 2D/CV, but in practice there's - // a lot of duplicated and zero values (see below). - var swizzleScratch = new Cartesian3(); - function swizzle(cartesian) { - Cartesian3.clone(cartesian, swizzleScratch); - cartesian.x = swizzleScratch.z; - cartesian.y = swizzleScratch.x; - cartesian.z = swizzleScratch.y; - return cartesian; - } - var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); @@ -675,37 +663,37 @@ define([ carto.longitude = rectangle.west; carto.latitude = rectangle.south; - var southWestCorner = swizzle(projection.project(carto, cornerScratch)); + var southWestCorner = projection.project(carto, cornerScratch); carto.latitude = rectangle.north; - var northWest = swizzle(projection.project(carto, northWestScratch)); + var northWest = projection.project(carto, northWestScratch); carto.longitude = rectangle.east; carto.latitude = rectangle.south; - var southEast = swizzle(projection.project(carto, southEastScratch)); + 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: y value for southWestCorner - // y: z value for southWestCorner - // z: z value for northWest - // w: y value for southEast + // x: x value for southWestCorner + // y: y value for southWestCorner + // z: y value for northWest + // w: x value for southEast var valuesHigh = [0, 0, 0, 0]; var valuesLow = [0, 0, 0, 0]; - var encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); + var encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch); valuesHigh[0] = encoded.high; valuesLow[0] = encoded.low; - encoded = EncodedCartesian3.encode(southWestCorner.z, highLowScratch); + encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch); valuesHigh[1] = encoded.high; valuesLow[1] = encoded.low; - encoded = EncodedCartesian3.encode(northWest.z, highLowScratch); + encoded = EncodedCartesian3.encode(northWest.y, highLowScratch); valuesHigh[2] = encoded.high; valuesLow[2] = encoded.low; - encoded = EncodedCartesian3.encode(southEast.y, highLowScratch); + encoded = EncodedCartesian3.encode(southEast.x, highLowScratch); valuesHigh[3] = encoded.high; valuesLow[3] = encoded.low; diff --git a/Specs/Core/RectangleCollisionCheckerSpec.js b/Specs/Core/RectangleCollisionCheckerSpec.js new file mode 100644 index 00000000000..393f7b72aeb --- /dev/null +++ b/Specs/Core/RectangleCollisionCheckerSpec.js @@ -0,0 +1,35 @@ +defineSuite([ + 'Core/RectangleCollisionChecker', + 'Core/Rectangle' + ], function( + RectangleCollisionChecker, + Rectangle) { + 'use strict'; + + var testRectangle1 = new Rectangle(0.0, 0.0, 1.0, 1.0); + var testRectangle2 = new Rectangle(1.1, 1.1, 2.1, 2.1); + var testRectangle3 = new Rectangle(1.1, 1.1, 1.2, 1.2); + + it('Checks for collisions with contained rectangles', function() { + var collisionChecker = new RectangleCollisionChecker(); + collisionChecker.insert('test1', testRectangle1); + + expect(collisionChecker.collides(testRectangle2)).toBe(false); + + collisionChecker.insert('test3', testRectangle3); + expect(collisionChecker.collides(testRectangle2)).toBe(true); + }); + + it('removes rectangles', function() { + var collisionChecker = new RectangleCollisionChecker(); + collisionChecker.insert('test1', testRectangle1); + + collisionChecker.insert('test3', testRectangle3); + + expect(collisionChecker.collides(testRectangle2)).toBe(true); + + collisionChecker.remove('test3', testRectangle3); + + expect(collisionChecker.collides(testRectangle2)).toBe(false); + }); +}); diff --git a/Specs/DataSources/GeometryVisualizerSpec.js b/Specs/DataSources/GeometryVisualizerSpec.js index 689ea3df8aa..f2c35ededa9 100644 --- a/Specs/DataSources/GeometryVisualizerSpec.js +++ b/Specs/DataSources/GeometryVisualizerSpec.js @@ -19,7 +19,6 @@ defineSuite([ 'DataSources/SampledProperty', 'DataSources/StaticGeometryColorBatch', 'DataSources/StaticGeometryPerMaterialBatch', - 'DataSources/StaticGroundGeometryColorBatch', 'DataSources/StaticOutlineGeometryBatch', 'Scene/ClassificationType', 'Scene/GroundPrimitive', @@ -50,7 +49,6 @@ defineSuite([ SampledProperty, StaticGeometryColorBatch, StaticGeometryPerMaterialBatch, - StaticGroundGeometryColorBatch, StaticOutlineGeometryBatch, ClassificationType, GroundPrimitive, @@ -140,6 +138,7 @@ defineSuite([ ellipse.semiMajorAxis = new ConstantProperty(2); ellipse.semiMinorAxis = new ConstantProperty(1); ellipse.material = new GridMaterialProperty(); + ellipse.height = new ConstantProperty(1.0); var entity = new Entity(); entity.position = new ConstantPositionProperty(new Cartesian3(1234, 5678, 9101112)); @@ -807,7 +806,8 @@ defineSuite([ semiMinorAxis : 1, material : new GridMaterialProperty({ color : createDynamicProperty(Color.BLUE) - }) + }), + height : 0.0 } }); diff --git a/Specs/DataSources/PolylineVisualizerSpec.js b/Specs/DataSources/PolylineVisualizerSpec.js index b213f638c18..49a2695a29f 100644 --- a/Specs/DataSources/PolylineVisualizerSpec.js +++ b/Specs/DataSources/PolylineVisualizerSpec.js @@ -19,7 +19,6 @@ defineSuite([ 'DataSources/SampledProperty', 'DataSources/StaticGeometryColorBatch', 'DataSources/StaticGeometryPerMaterialBatch', - 'DataSources/StaticGroundGeometryColorBatch', 'DataSources/StaticOutlineGeometryBatch', 'Scene/PolylineColorAppearance', 'Scene/PolylineMaterialAppearance', @@ -48,7 +47,6 @@ defineSuite([ SampledProperty, StaticGeometryColorBatch, StaticGeometryPerMaterialBatch, - StaticGroundGeometryColorBatch, StaticOutlineGeometryBatch, PolylineColorAppearance, PolylineMaterialAppearance, diff --git a/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js b/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js deleted file mode 100644 index 32d95f9f05f..00000000000 --- a/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js +++ /dev/null @@ -1,239 +0,0 @@ -defineSuite([ - 'DataSources/StaticGroundGeometryColorBatch', - 'Core/Cartesian3', - 'Core/Color', - 'Core/DistanceDisplayCondition', - 'Core/JulianDate', - 'Core/Math', - 'Core/TimeInterval', - 'Core/TimeIntervalCollection', - 'DataSources/CallbackProperty', - 'DataSources/ColorMaterialProperty', - 'DataSources/EllipseGeometryUpdater', - 'DataSources/Entity', - 'DataSources/TimeIntervalCollectionProperty', - 'Scene/ClassificationType', - 'Scene/GroundPrimitive', - 'Specs/createScene', - 'Specs/pollToPromise' - ], function( - StaticGroundGeometryColorBatch, - Cartesian3, - Color, - DistanceDisplayCondition, - JulianDate, - CesiumMath, - TimeInterval, - TimeIntervalCollection, - CallbackProperty, - ColorMaterialProperty, - EllipseGeometryUpdater, - Entity, - TimeIntervalCollectionProperty, - ClassificationType, - GroundPrimitive, - createScene, - pollToPromise) { - 'use strict'; - - var time = JulianDate.now(); - var scene; - beforeAll(function() { - scene = createScene(); - - return GroundPrimitive.initializeTerrainHeights(); - }); - - afterAll(function() { - scene.destroyForSpecs(); - - // Leave ground primitive uninitialized - GroundPrimitive._initialized = false; - GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; - }); - - function computeKey(color) { - var ui8 = new Uint8Array(color); - var ui32 = new Uint32Array(ui8.buffer); - return ui32[0]; - } - - it('updates color attribute after rebuilding primitive', function() { - if (!GroundPrimitive.isSupported(scene)) { - return; - } - - var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); - var entity = new Entity({ - position : new Cartesian3(1234, 5678, 9101112), - ellipse : { - semiMajorAxis : 2, - semiMinorAxis : 1, - show : new CallbackProperty(function() { - return true; - }, false), - material : Color.RED - } - }); - - var updater = new EllipseGeometryUpdater(entity, scene); - batch.add(time, updater); - - return pollToPromise(function() { - scene.initializeFrame(); - var isUpdated = batch.update(time); - scene.render(time); - return isUpdated; - }).then(function() { - expect(scene.groundPrimitives.length).toEqual(1); - var primitive = scene.groundPrimitives.get(0); - var attributes = primitive.getGeometryInstanceAttributes(entity); - var red = [255, 0, 0, 255]; - var redKey = computeKey(red); - expect(attributes.color).toEqual(red); - - // Verify we have 1 batch with the key for red - expect(batch._batches.length).toEqual(1); - expect(batch._batches.contains(redKey)).toBe(true); - expect(batch._batches.get(redKey).key).toEqual(redKey); - - entity.ellipse.material = Color.GREEN; - updater._onEntityPropertyChanged(entity, 'ellipse'); - batch.remove(updater); - batch.add(time, updater); - return pollToPromise(function() { - scene.initializeFrame(); - var isUpdated = batch.update(time); - scene.render(time); - return isUpdated; - }).then(function() { - expect(scene.groundPrimitives.length).toEqual(1); - var primitive = scene.groundPrimitives.get(0); - var attributes = primitive.getGeometryInstanceAttributes(entity); - var green = [0, 128, 0, 255]; - var greenKey = computeKey(green); - expect(attributes.color).toEqual(green); - - // Verify we have 1 batch with the key for green - expect(batch._batches.length).toEqual(1); - expect(batch._batches.contains(greenKey)).toBe(true); - expect(batch._batches.get(greenKey).key).toEqual(greenKey); - - batch.removeAllPrimitives(); - }); - }); - }); - - it('updates with sampled distance display condition out of range', function() { - var validTime = JulianDate.fromIso8601('2018-02-14T04:10:00+1100'); - var ddc = new TimeIntervalCollectionProperty(); - ddc.intervals.addInterval(TimeInterval.fromIso8601({ - iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100', - data: new DistanceDisplayCondition(1.0, 2.0) - })); - var entity = new Entity({ - availability: new TimeIntervalCollection([TimeInterval.fromIso8601({iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100'})]), - position : new Cartesian3(1234, 5678, 9101112), - ellipse: { - semiMajorAxis : 2, - semiMinorAxis : 1, - material: Color.RED, - distanceDisplayCondition: ddc - } - }); - - var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); - - var updater = new EllipseGeometryUpdater(entity, scene); - batch.add(validTime, updater); - - return pollToPromise(function() { - scene.initializeFrame(); - var isUpdated = batch.update(validTime); - scene.render(validTime); - return isUpdated; - }).then(function() { - expect(scene.groundPrimitives.length).toEqual(1); - var primitive = scene.groundPrimitives.get(0); - var attributes = primitive.getGeometryInstanceAttributes(entity); - expect(attributes.distanceDisplayCondition).toEqualEpsilon([1.0, 2.0], CesiumMath.EPSILON6); - - batch.update(time); - scene.render(time); - - primitive = scene.groundPrimitives.get(0); - attributes = primitive.getGeometryInstanceAttributes(entity); - expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]); - - batch.removeAllPrimitives(); - }); - }); - - it('shows only one primitive while rebuilding primitive', function() { - if (!GroundPrimitive.isSupported(scene)) { - return; - } - - var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); - function buildEntity() { - return new Entity({ - position : new Cartesian3(1234, 5678, 9101112), - ellipse : { - semiMajorAxis : 2, - semiMinorAxis : 1, - height : 0, - outline : true, - outlineColor : Color.RED.withAlpha(0.5) - } - }); - } - - function renderScene() { - scene.initializeFrame(); - var isUpdated = batch.update(time); - scene.render(time); - return isUpdated; - } - - var entity1 = buildEntity(); - var entity2 = buildEntity(); - - var updater1 = new EllipseGeometryUpdater(entity1, scene); - var updater2 = new EllipseGeometryUpdater(entity2, scene); - - batch.add(time, updater1); - return pollToPromise(renderScene) - .then(function() { - expect(scene.groundPrimitives.length).toEqual(1); - var primitive = scene.groundPrimitives.get(0); - expect(primitive.show).toBeTruthy(); - }) - .then(function() { - batch.add(time, updater2); - }) - .then(function() { - return pollToPromise(function() { - renderScene(); - return scene.groundPrimitives.length === 2; - }); - }) - .then(function() { - var showCount = 0; - expect(scene.groundPrimitives.length).toEqual(2); - showCount += !!scene.groundPrimitives.get(0).show; - showCount += !!scene.groundPrimitives.get(1).show; - expect(showCount).toEqual(1); - }) - .then(function() { - return pollToPromise(renderScene); - }) - .then(function() { - expect(scene.groundPrimitives.length).toEqual(1); - var primitive = scene.groundPrimitives.get(0); - expect(primitive.show).toBeTruthy(); - - batch.removeAllPrimitives(); - }); - }); -}); diff --git a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js new file mode 100644 index 00000000000..ba6fd15bb2f --- /dev/null +++ b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js @@ -0,0 +1,306 @@ +defineSuite([ + 'DataSources/StaticGroundGeometryPerMaterialBatch', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Color', + 'Core/DistanceDisplayCondition', + 'Core/JulianDate', + 'Core/Math', + 'Core/TimeInterval', + 'Core/TimeIntervalCollection', + 'DataSources/ColorMaterialProperty', + 'DataSources/ConstantPositionProperty', + 'DataSources/ConstantProperty', + 'DataSources/EllipseGeometryUpdater', + 'DataSources/EllipseGraphics', + 'DataSources/Entity', + 'DataSources/GridMaterialProperty', + 'DataSources/PolylineArrowMaterialProperty', + 'DataSources/PolylineGeometryUpdater', + 'DataSources/PolylineGraphics', + 'DataSources/TimeIntervalCollectionProperty', + 'Scene/ClassificationType', + 'Scene/MaterialAppearance', + 'Scene/PolylineColorAppearance', + 'Scene/PolylineMaterialAppearance', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + StaticGroundGeometryPerMaterialBatch, + Cartesian2, + Cartesian3, + Color, + DistanceDisplayCondition, + JulianDate, + CesiumMath, + TimeInterval, + TimeIntervalCollection, + ColorMaterialProperty, + ConstantPositionProperty, + ConstantProperty, + EllipseGeometryUpdater, + EllipseGraphics, + Entity, + GridMaterialProperty, + PolylineArrowMaterialProperty, + PolylineGeometryUpdater, + PolylineGraphics, + TimeIntervalCollectionProperty, + ClassificationType, + MaterialAppearance, + PolylineColorAppearance, + PolylineMaterialAppearance, + createScene, + pollToPromise) { + 'use strict'; + + var time = JulianDate.now(); + var scene; + beforeAll(function() { + scene = createScene(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + it('handles shared material being invalidated with geometry', function() { + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + + var ellipse = new EllipseGraphics(); + ellipse.semiMajorAxis = new ConstantProperty(2); + ellipse.semiMinorAxis = new ConstantProperty(1); + ellipse.material = new GridMaterialProperty(); + + var entity = new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : ellipse + }); + + var ellipse2 = new EllipseGraphics(); + ellipse2.semiMajorAxis = new ConstantProperty(3); + ellipse2.semiMinorAxis = new ConstantProperty(2); + ellipse2.material = new GridMaterialProperty(); + + var entity2 = new Entity({ + position : new Cartesian3(123, 456, 789), + ellipse : ellipse2 + }); + + var updater = new EllipseGeometryUpdater(entity, scene); + var updater2 = new EllipseGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.primitives.length).toEqual(1); + ellipse.material.cellAlpha = new ConstantProperty(0.5); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.primitives.length).toEqual(2); + batch.removeAllPrimitives(); + }); + }); + }); + + it('updates with sampled distance display condition out of range', function() { + var validTime = JulianDate.fromIso8601('2018-02-14T04:10:00+1100'); + var ddc = new TimeIntervalCollectionProperty(); + ddc.intervals.addInterval(TimeInterval.fromIso8601({ + iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100', + data: new DistanceDisplayCondition(1.0, 2.0) + })); + var entity = new Entity({ + availability: new TimeIntervalCollection([TimeInterval.fromIso8601({iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100'})]), + position : new Cartesian3(1234, 5678, 9101112), + ellipse: { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : new GridMaterialProperty(), + distanceDisplayCondition : ddc + } + }); + + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + + var updater = new EllipseGeometryUpdater(entity, scene); + batch.add(validTime, updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(validTime); + scene.render(validTime); + return isUpdated; + }).then(function() { + expect(scene.primitives.length).toEqual(1); + var primitive = scene.primitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqualEpsilon([1.0, 2.0], CesiumMath.EPSILON6); + + batch.update(time); + scene.render(time); + + primitive = scene.primitives.get(0); + attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]); + + batch.removeAllPrimitives(); + }); + }); + + it('shows only one primitive while rebuilding primitive', function() { + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + + function buildEntity(x, y, z) { + var material = new GridMaterialProperty({ + color : Color.YELLOW, + cellAlpha : 0.3, + lineCount : new Cartesian2(8, 8), + lineThickness : new Cartesian2(2.0, 2.0) + }); + + return new Entity({ + position : new Cartesian3(x, y, z), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material: material + } + }); + } + + function renderScene() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + } + + var entity1 = buildEntity(1234, 5678, 9101112); + var entity2 = buildEntity(123, 456, 789); + + var updater1 = new EllipseGeometryUpdater(entity1, scene); + var updater2 = new EllipseGeometryUpdater(entity2, scene); + + batch.add(time, updater1); + return pollToPromise(renderScene) + .then(function() { + expect(scene.primitives.length).toEqual(1); + var primitive = scene.primitives.get(0); + expect(primitive.show).toBeTruthy(); + }) + .then(function() { + batch.add(time, updater2); + }) + .then(function() { + return pollToPromise(function() { + renderScene(); + return scene.primitives.length === 2; + }); + }) + .then(function() { + var showCount = 0; + expect(scene.primitives.length).toEqual(2); + showCount += !!scene.primitives.get(0).show; + showCount += !!scene.primitives.get(1).show; + expect(showCount).toEqual(1); + }) + .then(function() { + return pollToPromise(renderScene); + }) + .then(function() { + expect(scene.primitives.length).toEqual(1); + var primitive = scene.primitives.get(0); + expect(primitive.show).toBeTruthy(); + + batch.removeAllPrimitives(); + }); + }); + + it('batches overlapping entities with the same material separately', function() { + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + + var ellipse = new EllipseGraphics(); + ellipse.semiMajorAxis = new ConstantProperty(2); + ellipse.semiMinorAxis = new ConstantProperty(1); + ellipse.material = new GridMaterialProperty(); + + var entity = new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : ellipse + }); + + var ellipse2 = new EllipseGraphics(); + ellipse2.semiMajorAxis = new ConstantProperty(3); + ellipse2.semiMinorAxis = new ConstantProperty(2); + ellipse2.material = new GridMaterialProperty(); + + var entity2 = new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : ellipse2 + }); + + var updater = new EllipseGeometryUpdater(entity, scene); + var updater2 = new EllipseGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.primitives.length).toEqual(2); + + batch.removeAllPrimitives(); + }); + }); + + it('batches nonoverlapping entities that both use color materials', function() { + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var entity = new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : Color.RED + } + }); + + var entity2 = new Entity({ + position : new Cartesian3(123, 456, 789), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : Color.BLUE + } + }); + + var updater = new EllipseGeometryUpdater(entity, scene); + var updater2 = new EllipseGeometryUpdater(entity2, scene); + batch.add(time, updater); + batch.add(time, updater2); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.primitives.length).toEqual(1); + + batch.removeAllPrimitives(); + }); + }); +}); diff --git a/Specs/Scene/ClassificationPrimitiveSpec.js b/Specs/Scene/ClassificationPrimitiveSpec.js index 3b5f29abef2..41b5ffabb59 100644 --- a/Specs/Scene/ClassificationPrimitiveSpec.js +++ b/Specs/Scene/ClassificationPrimitiveSpec.js @@ -14,8 +14,10 @@ defineSuite([ 'Core/Transforms', 'Renderer/Pass', 'Scene/InvertClassification', + 'Scene/MaterialAppearance', 'Scene/PerInstanceColorAppearance', 'Scene/Primitive', + 'Scene/ShadowVolumeAppearance', 'Specs/createScene', 'Specs/pollToPromise' ], function( @@ -34,8 +36,10 @@ defineSuite([ Transforms, Pass, InvertClassification, + MaterialAppearance, PerInstanceColorAppearance, Primitive, + ShadowVolumeAppearance, createScene, pollToPromise) { 'use strict'; @@ -90,6 +94,7 @@ defineSuite([ beforeEach(function() { scene.morphTo3D(0); + scene.render(); // clear any afterRender commands rectangle = Rectangle.fromDegrees(-75.0, 25.0, -70.0, 30.0); @@ -773,7 +778,7 @@ defineSuite([ }); }); - it('update throws when batched instance colors are different', function() { + it('update throws when one batched instance color is undefined', function() { if (!ClassificationPrimitive.isSupported(scene)) { return; } @@ -818,10 +823,7 @@ defineSuite([ dimensions : dimensions }), modelMatrix : modelMatrix, - id : 'box2', - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 0.0, 1.0, 1.0)) - } + id : 'box2' }); primitive = new ClassificationPrimitive({ @@ -834,7 +836,7 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('update throws when one batched instance color is undefined', function() { + it('update throws when no batched instance colors are given for a PerInstanceColorAppearance', function() { if (!ClassificationPrimitive.isSupported(scene)) { return; } @@ -858,37 +860,69 @@ defineSuite([ var dimensions = new Cartesian3(500000.0, 1000000.0, 1000000.0); - var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); var boxInstance1 = new GeometryInstance({ geometry : BoxGeometry.fromDimensions({ dimensions : dimensions }), modelMatrix : modelMatrix, - id : 'box1', - attributes : { - color : ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)) - } + id : 'box1' }); - Cartesian3.negate(direction, direction); - var origin2 = Cartesian3.add(origin, direction, new Cartesian3()); - modelMatrix = Transforms.eastNorthUpToFixedFrame(origin2); + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1], + asynchronous : false, + appearance : new PerInstanceColorAppearance() + }); - var boxInstance2 = new GeometryInstance({ - geometry : BoxGeometry.fromDimensions({ - dimensions : dimensions - }), - modelMatrix : modelMatrix, - id : 'box2' + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + + expect(function() { + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }).toThrowDeveloperError(); + }); + + it('update throws when the given Appearance is incompatible with the geometry instance attributes', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance], + asynchronous : false, + appearance : new MaterialAppearance() }); + expect(function() { + verifyClassificationPrimitiveRender(primitive, [255, 255, 255, 255]); + }).toThrowDeveloperError(); + }); + + it('update throws when an incompatible Appearance is set', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + primitive = new ClassificationPrimitive({ - geometryInstances : [boxInstance1, boxInstance2], - asynchronous : false + geometryInstances : [boxInstance], + asynchronous : false, + appearance : new PerInstanceColorAppearance() + }); + + scene.camera.setView({ destination : rectangle }); + scene.groundPrimitives.add(depthPrimitive); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba[0]).toEqual(0); }); + scene.groundPrimitives.add(primitive); + expect(scene).toRender([255, 255, 0, 255]); + + // become incompatible + primitive.appearance = new MaterialAppearance(); + expect(function() { - verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + expect(scene).toRender([255, 255, 255, 255]); }).toThrowDeveloperError(); }); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index e924ed589bf..04e240ef418 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -13,9 +13,12 @@ defineSuite([ 'Core/RectangleGeometry', 'Core/ShowGeometryInstanceAttribute', 'Renderer/Pass', + 'Scene/EllipsoidSurfaceAppearance', 'Scene/InvertClassification', + 'Scene/Material', 'Scene/PerInstanceColorAppearance', 'Scene/Primitive', + 'Specs/createCanvas', 'Specs/createScene', 'Specs/pollToPromise' ], function( @@ -33,9 +36,12 @@ defineSuite([ RectangleGeometry, ShowGeometryInstanceAttribute, Pass, + EllipsoidSurfaceAppearance, InvertClassification, + Material, PerInstanceColorAppearance, Primitive, + createCanvas, createScene, pollToPromise) { 'use strict'; @@ -99,6 +105,7 @@ defineSuite([ beforeEach(function() { scene.morphTo3D(0); + scene.render(); // clear any afterRender commands rectangle = Rectangle.fromDegrees(-80.0, 20.0, -70.0, 30.0); @@ -380,6 +387,47 @@ defineSuite([ verifyGroundPrimitiveRender(primitive, rectColor); }); + // Screen space techniques may produce unexpected results with 1x1 canvasses + function verifyLargerScene(groundPrimitive, expectedColor, destination) { + var largeScene = createScene({ + canvas : createCanvas(2, 2) + }); + largeScene.render(); + + largeScene.fxaa = false; + largeScene.camera.setView({ destination : destination }); + + var largeSceneDepthPrimitive = new MockGlobePrimitive(new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : rectangle + }), + id : 'depth rectangle', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)) + } + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + })); + + largeScene.groundPrimitives.add(largeSceneDepthPrimitive); + expect(largeScene).toRenderAndCall(function(rgba) { + expect(rgba.slice(0, 4)).not.toEqual([0, 0, 0, 255]); + expect(rgba[0]).toEqual(0); + }); + + largeScene.groundPrimitives.add(groundPrimitive); + expect(largeScene).toRenderAndCall(function(rgba) { + expect(rgba.slice(0, 4)).toEqual(expectedColor); + }); + largeScene.destroyForSpecs(); + } + it('renders batched instances', function() { if (!GroundPrimitive.isSupported(scene)) { return; @@ -407,11 +455,74 @@ defineSuite([ } }); - primitive = new GroundPrimitive({ + var batchedPrimitive = new GroundPrimitive({ geometryInstances : [rectangleInstance1, rectangleInstance2], asynchronous : false }); - verifyGroundPrimitiveRender(primitive, rectColorAttribute.value); + + verifyLargerScene(batchedPrimitive, [0, 255, 255, 255], rectangle); + }); + + it('renders small GeometryInstances with texture', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + var whiteImageMaterial = Material.fromType(Material.DiffuseMapType); + whiteImageMaterial.uniforms.image = './Data/Images/White.png'; + + var radians = CesiumMath.toRadians(0.1); + var west = rectangle.west; + var south = rectangle.south; + var smallRectangle = new Rectangle(west, south, west + radians, south + radians); + var smallRectanglePrimitive = new GroundPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : smallRectangle + }), + id : 'smallRectangle' + }), + appearance : new EllipsoidSurfaceAppearance({ + aboveGround : false, + flat : true, + material : whiteImageMaterial + }), + asynchronous : false + }); + + verifyLargerScene(smallRectanglePrimitive, [255, 255, 255, 255], smallRectangle); + }); + + it('renders large GeometryInstances with texture', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + var whiteImageMaterial = Material.fromType(Material.DiffuseMapType); + whiteImageMaterial.uniforms.image = './Data/Images/White.png'; + + var radians = CesiumMath.toRadians(1.1); + var west = rectangle.west; + var south = rectangle.south; + var largeRectangle = new Rectangle(west, south, west + radians, south + radians); + var largeRectanglePrimitive = new GroundPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : largeRectangle + }) + }), + id : 'largeRectangle', + appearance : new EllipsoidSurfaceAppearance({ + aboveGround : false, + flat : true, + material : whiteImageMaterial + }), + asynchronous : false + }); + + verifyLargerScene(largeRectanglePrimitive, [255, 255, 255, 255], largeRectangle); }); it('renders with invert classification and an opaque color', function() { @@ -642,7 +753,7 @@ defineSuite([ attributes : { color : rectColorAttribute, distanceDisplayCondition : new DistanceDisplayConditionGeometryInstanceAttribute(near, far) - } // Dan: attributes added here "automagically" show up in the shader in the batch table. Look at DistanceDisplayConditionGeometryInstanceAttribute + } }); primitive = new GroundPrimitive({ @@ -702,7 +813,7 @@ defineSuite([ expect(attributes).toBe(attributes2); }); - it('picking', function() { + it('picking in 3D', function() { if (!GroundPrimitive.isSupported(scene)) { return; } @@ -719,6 +830,42 @@ defineSuite([ }); }); + it('picking in 2D', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + primitive = new GroundPrimitive({ + geometryInstances : rectangleInstance, + asynchronous : false + }); + + scene.morphTo2D(0); + verifyGroundPrimitiveRender(primitive, rectColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('rectangle'); + }); + }); + + it('picking in Columbus View', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + primitive = new GroundPrimitive({ + geometryInstances : rectangleInstance, + asynchronous : false + }); + + scene.morphToColumbusView(0); + verifyGroundPrimitiveRender(primitive, rectColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('rectangle'); + }); + }); + it('does not pick when allowPicking is false', function() { if (!GroundPrimitive.isSupported(scene)) { return; @@ -802,44 +949,6 @@ defineSuite([ }); }); - it('update throws when batched instance colors are different', function() { - if (!GroundPrimitive.isSupported(scene)) { - return; - } - - var rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); - var rectangleInstance1 = new GeometryInstance({ - geometry : new RectangleGeometry({ - ellipsoid : ellipsoid, - rectangle : new Rectangle(rectangle.west, rectangle.south, rectangle.east, (rectangle.north + rectangle.south) * 0.5) - }), - id : 'rectangle1', - attributes : { - color : rectColorAttribute - } - }); - rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); - var rectangleInstance2 = new GeometryInstance({ - geometry : new RectangleGeometry({ - ellipsoid : ellipsoid, - rectangle : new Rectangle(rectangle.west, (rectangle.north + rectangle.south) * 0.5, rectangle.east, rectangle.north) - }), - id : 'rectangle2', - attributes : { - color : rectColorAttribute - } - }); - - primitive = new GroundPrimitive({ - geometryInstances : [rectangleInstance1, rectangleInstance2], - asynchronous : false - }); - - expect(function() { - verifyGroundPrimitiveRender(primitive, rectColorAttribute.value); - }).toThrowDeveloperError(); - }); - it('update throws when one batched instance color is undefined', function() { if (!GroundPrimitive.isSupported(scene)) { return; diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index dc04e260b31..b83a9890fde 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -715,6 +715,7 @@ defineSuite([ var uniformState = scene.context.uniformState; + scene.copyGlobeDepth = false; expect(scene).toRenderAndCall(function(rgba) { expect(uniformState.globeDepthTexture).not.toBeDefined(); }); diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js new file mode 100644 index 00000000000..8b0ea6ef8ea --- /dev/null +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -0,0 +1,297 @@ +defineSuite([ + 'Scene/ShadowVolumeAppearance', + 'Core/Cartographic', + 'Core/Math', + 'Core/ComponentDatatype', + 'Core/Ellipsoid', + 'Core/WebMercatorProjection', + 'Core/Rectangle', + 'Scene/Material', + 'Scene/MaterialAppearance', + 'Scene/PerInstanceColorAppearance' +], function( + ShadowVolumeAppearance, + Cartographic, + CesiumMath, + ComponentDatatype, + Ellipsoid, + WebMercatorProjection, + Rectangle, + Material, + MaterialAppearance, + PerInstanceColorAppearance) { +'use strict'; + + // using ShadowVolumeVS directly fails on Travis with the --release test + var testVs = + 'attribute vec3 position3DHigh;\n' + + 'attribute vec3 position3DLow;\n' + + 'attribute float batchId;\n' + + 'void main() {\n' + + ' vec4 position = czm_computePosition();\n' + + ' gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position);\n' + + '}\n'; + + var unitSphereEllipsoid = Ellipsoid.UNIT_SPHERE; + var projection = new WebMercatorProjection(unitSphereEllipsoid); + 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, unitSphereEllipsoid, projection); + var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection); + + var perInstanceColorMaterialAppearance = new PerInstanceColorAppearance(); + var flatPerInstanceColorMaterialAppearance = new PerInstanceColorAppearance({ + flat : true + }); + + var textureMaterialAppearance = new MaterialAppearance({ + material : new Material({ + fabric : { + type : 'BumpMap', + uniforms : { + image : '../images/Cesium_Logo_Color.jpg', + channel : 'r' + } + } + }) + }); + var flatTextureMaterialAppearance = new MaterialAppearance({ + material : Material.fromType(Material.ImageType, { + image : '../Data/images/Red16x16.png' + }), + flat :true + }); + + it('provides attributes for computing texture coordinates from Spherical extents', function() { + var attributes = largeRectangleAttributes; + + var sphericalExtents = attributes.sphericalExtents; + expect(sphericalExtents.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(sphericalExtents.componentsPerAttribute).toEqual(4); + expect(sphericalExtents.normalize).toEqual(false); + var value = sphericalExtents.value; + expect(value[0]).toEqualEpsilon(-CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON4); + expect(value[1]).toEqualEpsilon(-CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON4); + expect(value[2]).toEqualEpsilon(1.0 / CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON4); + expect(value[3]).toEqualEpsilon(1.0 / CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON4); + }); + + function checkGeometryInstanceAttributePoint(attribute) { + expect(attribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(attribute.componentsPerAttribute).toEqual(3); + expect(attribute.normalize).toEqual(false); + } + + function checkPointLow(value, cosineAngle) { + // Basically a dot product with UNIT_X. + // Cosines of angles get smaller from 0 to 90 degrees, so this is a GreaterThan check. + expect(value[0]).toBeGreaterThan(cosineAngle); + } + + function checkPointHigh(value) { + expect(value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(value[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + expect(value[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); + } + + it('provides attributes for computing texture coordinates using planes in 3D', function() { + var attributes = smallRectangleAttributes; + + var southWest_LOW = attributes.southWest_LOW; + var northWest_LOW = attributes.northWest_LOW; + var southEast_LOW = attributes.southEast_LOW; + var southWest_HIGH = attributes.southWest_HIGH; + var northWest_HIGH = attributes.northWest_HIGH; + var southEast_HIGH = attributes.southEast_HIGH; + + checkGeometryInstanceAttributePoint(southWest_LOW); + checkGeometryInstanceAttributePoint(northWest_LOW); + checkGeometryInstanceAttributePoint(southEast_LOW); + checkGeometryInstanceAttributePoint(southWest_HIGH); + checkGeometryInstanceAttributePoint(northWest_HIGH); + checkGeometryInstanceAttributePoint(southEast_HIGH); + + // We're using a unit sphere, so expect all HIGH values to be basically 0 + // and LOW values to be within a small cone around UNIT_X + checkPointHigh(southWest_HIGH.value); + checkPointHigh(northWest_HIGH.value); + checkPointHigh(southEast_HIGH.value); + + var cosineAngle = Math.cos(CesiumMath.toRadians(0.2)); + checkPointLow(southWest_LOW.value, cosineAngle); + checkPointLow(northWest_LOW.value, cosineAngle); + checkPointLow(southEast_LOW.value, cosineAngle); + }); + + 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 rotating texture coordinates', function() { + var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, CesiumMath.PI_OVER_TWO); + + var stRotationAttribute = attributes.stSineCosineUVScale; + expect(stRotationAttribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(stRotationAttribute.componentsPerAttribute).toEqual(4); + expect(stRotationAttribute.normalize).toEqual(false); + + var value = stRotationAttribute.value; + expect(value[0]).toEqualEpsilon(Math.sin(CesiumMath.PI_OVER_TWO), CesiumMath.EPSILON7); + expect(value[1]).toEqualEpsilon(Math.cos(CesiumMath.PI_OVER_TWO), CesiumMath.EPSILON7); + expect(value[2]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); // 90 degree rotation of a square, so no scale + expect(value[3]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); + + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, CesiumMath.PI_OVER_FOUR); + value = attributes.stSineCosineUVScale.value; + expect(value[0]).toEqualEpsilon(Math.sin(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); + expect(value[1]).toEqualEpsilon(Math.cos(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); + + var expectedScale = Math.sqrt(2.0) * 0.5; // 45 degree rotation of a square, so scale to square diagonal + expect(value[2]).toEqualEpsilon(expectedScale, CesiumMath.EPSILON7); + expect(value[3]).toEqualEpsilon(expectedScale, CesiumMath.EPSILON7); + }); + + it('checks for spherical extent attributes', function() { + expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(smallRectangleAttributes)).toBe(false); + expect(ShadowVolumeAppearance.hasAttributesForSphericalExtents(largeRectangleAttributes)).toBe(true); + }); + + it('checks for planar texture coordinate attributes', function() { + expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(smallRectangleAttributes)).toBe(true); + expect(ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(largeRectangleAttributes)).toBe(false); + }); + + it('checks if a rectangle should use spherical texture coordinates', function() { + expect(ShadowVolumeAppearance.shouldUseSphericalCoordinates(smallTestRectangle)).toBe(false); + expect(ShadowVolumeAppearance.shouldUseSphericalCoordinates(largeTestRectangle)).toBe(true); + }); + + it('creates vertex shaders', function() { + // Check for varying declarations and that they get set + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var sphericalTexturedVS3D = sphericalTexturedAppearance.createVertexShader(testVs, false); + expect(sphericalTexturedVS3D.includes('varying vec4 v_sphericalExtents;')).toBe(true); + expect(sphericalTexturedVS3D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); + + expect(sphericalTexturedVS3D.includes('v_sphericalExtents =')).toBe(true); + expect(sphericalTexturedVS3D.includes('v_stSineCosineUVScale =')).toBe(true); + + var sphericalTexturedVS2D = sphericalTexturedAppearance.createVertexShader(testVs, true); + expect(sphericalTexturedVS2D.includes('varying vec2 v_inversePlaneExtents;')).toBe(true); + expect(sphericalTexturedVS2D.includes('varying vec4 v_westPlane;')).toBe(true); + expect(sphericalTexturedVS2D.includes('varying vec4 v_southPlane;')).toBe(true); + expect(sphericalTexturedVS2D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); + + expect(sphericalTexturedVS2D.includes('v_inversePlaneExtents =')).toBe(true); + expect(sphericalTexturedVS2D.includes('v_westPlane =')).toBe(true); + expect(sphericalTexturedVS2D.includes('v_southPlane =')).toBe(true); + expect(sphericalTexturedVS2D.includes('v_stSineCosineUVScale =')).toBe(true); + + var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance); + var sphericalUnculledColorVS3D = sphericalUnculledColorAppearance.createVertexShader(testVs, false); + expect(sphericalUnculledColorVS3D.includes('varying vec4 v_color;')).toBe(true); + expect(sphericalUnculledColorVS3D.includes('v_color =')).toBe(true); + + var sphericalUnculledColorVS2D = sphericalUnculledColorAppearance.createVertexShader(testVs, true); + expect(sphericalUnculledColorVS2D.includes('varying vec4 v_color;')).toBe(true); + expect(sphericalUnculledColorVS2D.includes('v_color =')).toBe(true); + + var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance); + var planarTexturedAppearanceVS3D = planarTexturedAppearance.createVertexShader(testVs, false); + + expect(planarTexturedAppearanceVS3D.includes('varying vec2 v_inversePlaneExtents;')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_westPlane;')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_southPlane;')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); + + expect(planarTexturedAppearanceVS3D.includes('v_inversePlaneExtents =')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('v_westPlane =')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('v_southPlane =')).toBe(true); + expect(planarTexturedAppearanceVS3D.includes('v_stSineCosineUVScale =')).toBe(true); + }); + + it('creates fragment shaders', function() { + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var sphericalTexturedFS3D = sphericalTexturedAppearance.createAppearanceFragmentShader(false); + + // Check material hookups, discard for culling, and phong shading + expect(sphericalTexturedFS3D.includes('.normalEC =')).toBe(true); + expect(sphericalTexturedFS3D.includes('.positionToEyeEC =')).toBe(true); + expect(sphericalTexturedFS3D.includes('.tangentToEyeMatrix =')).toBe(true); + expect(sphericalTexturedFS3D.includes('.st.x =')).toBe(true); + expect(sphericalTexturedFS3D.includes('.st.y =')).toBe(true); + expect(sphericalTexturedFS3D.includes('discard;')).toBe(true); + expect(sphericalTexturedFS3D.includes('czm_phong')).toBe(true); + + var sphericalTexturedFS2D = sphericalTexturedAppearance.createAppearanceFragmentShader(true); + + expect(sphericalTexturedFS2D.includes('.normalEC =')).toBe(true); + expect(sphericalTexturedFS2D.includes('.positionToEyeEC =')).toBe(true); + expect(sphericalTexturedFS2D.includes('.tangentToEyeMatrix =')).toBe(true); + expect(sphericalTexturedFS2D.includes('.st.x =')).toBe(true); + expect(sphericalTexturedFS2D.includes('.st.y =')).toBe(true); + expect(sphericalTexturedFS2D.includes('discard;')).toBe(true); + expect(sphericalTexturedFS2D.includes('czm_phong')).toBe(true); + + var planarColorAppearance = new ShadowVolumeAppearance(true, false, perInstanceColorMaterialAppearance); + var planarColorFS3D = planarColorAppearance.createAppearanceFragmentShader(false); + expect(planarColorFS3D.includes('= v_color')).toBe(true); + expect(planarColorFS3D.includes('varying vec4 v_color;')).toBe(true); + expect(planarColorFS3D.includes('discard;')).toBe(true); + expect(planarColorFS3D.includes('czm_phong')).toBe(true); + + // Pick + var pickColorFS2D = planarColorAppearance.createPickingFragmentShader(true); + expect(pickColorFS2D.includes('gl_FragColor.a = 1.0')).toBe(true); + + // Flat + var flatSphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, flatTextureMaterialAppearance); + var flatSphericalTexturedFS3D = flatSphericalTexturedAppearance.createAppearanceFragmentShader(false); + expect(flatSphericalTexturedFS3D.includes('gl_FragColor = vec4')).toBe(true); + + var flatSphericalColorAppearance = new ShadowVolumeAppearance(false, false, flatPerInstanceColorMaterialAppearance); + var flatSphericalColorFS3D = flatSphericalColorAppearance.createAppearanceFragmentShader(false); + expect(flatSphericalColorFS3D.includes('gl_FragColor = v_color;')).toBe(true); + }); +}); diff --git a/Specs/createGeometryUpdaterGroundGeometrySpecs.js b/Specs/createGeometryUpdaterGroundGeometrySpecs.js index 79b5268e934..996e54e7c51 100644 --- a/Specs/createGeometryUpdaterGroundGeometrySpecs.js +++ b/Specs/createGeometryUpdaterGroundGeometrySpecs.js @@ -3,7 +3,6 @@ define([ 'Core/JulianDate', 'DataSources/ColorMaterialProperty', 'DataSources/ConstantProperty', - 'DataSources/GridMaterialProperty', 'DataSources/SampledProperty', 'Scene/GroundPrimitive', 'Scene/PrimitiveCollection' @@ -12,7 +11,6 @@ define([ JulianDate, ColorMaterialProperty, ConstantProperty, - GridMaterialProperty, SampledProperty, GroundPrimitive, PrimitiveCollection) { @@ -31,7 +29,7 @@ define([ expect(updater.isDynamic).toBe(true); }); - it('Checks that an entity without height and extrudedHeight and with a color material is on terrain', function() { + it('Checks that an entity without height and extrudedHeight is on terrain', function() { var entity = createEntity(); var geometry = entity[geometryPropertyName]; geometry.height = undefined; @@ -68,17 +66,6 @@ define([ expect(updater.onTerrain).toBe(false); }); - it('Checks that an entity with a non-color material isn\'t on terrain', function() { - var entity = createEntity(); - var geometry = entity[geometryPropertyName]; - geometry.height = undefined; - geometry.material = new GridMaterialProperty(Color.BLUE); - - var updater = new Updater(entity, getScene()); - - expect(updater.onTerrain).toBe(false); - }); - it('fill is true sets onTerrain to true', function() { var entity = createEntity(); entity[geometryPropertyName].fill = true; @@ -128,15 +115,6 @@ define([ } }); - it('non-color material sets onTerrain to false', function() { - var entity = createEntity(); - var geometry = entity[geometryPropertyName]; - geometry.fill = true; - geometry.material = new GridMaterialProperty(); - var updater = new Updater(entity, getScene()); - expect(updater.onTerrain).toBe(false); - }); - it('dynamic updater on terrain', function() { var entity = createDynamicEntity(); From 5e4f46cd09105b817d47ef6f3b393c34ed6f3e1d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 27 Apr 2018 15:41:35 -0400 Subject: [PATCH 24/40] updates for planar texcoords in 3D --- Source/Scene/ShadowVolumeAppearance.js | 177 +++++++++++++++++----- Specs/Scene/ShadowVolumeAppearanceSpec.js | 64 ++++---- 2 files changed, 167 insertions(+), 74 deletions(-) diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 6bdd807e143..fd4a19f6c9d 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -11,7 +11,9 @@ define([ '../Core/EncodedCartesian3', '../Core/GeometryInstanceAttribute', '../Core/Matrix2', + '../Core/Matrix4', '../Core/Rectangle', + '../Core/Transforms', '../Renderer/ShaderSource', '../Scene/PerInstanceColorAppearance' ], function( @@ -27,7 +29,9 @@ define([ EncodedCartesian3, GeometryInstanceAttribute, Matrix2, + Matrix4, Rectangle, + Transforms, ShaderSource, PerInstanceColorAppearance) { 'use strict'; @@ -404,8 +408,10 @@ define([ 'v_sphericalExtents = czm_batchTable_sphericalExtents(batchId);\n' + 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; } else { - // Two varieties of planar texcoords. 2D/CV case is "compressed" to fewer attributes + // Two varieties of planar texcoords if (columbusView2D) { + // 2D/CV case may have very large "plane extents," so planes and distances encoded as 3 64 bit positions, + // which in 2D can be encoded as 2 64 bit vec2s glsl += 'vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId);\n' + 'vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId);\n' + @@ -414,9 +420,10 @@ define([ '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;\n'; } else { glsl += + // 3D case has smaller "plane extents," so planes encoded as a 64 bit position and 2 vec3s for distances/direction 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz;\n' + - 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_northWest_HIGH(batchId), czm_batchTable_northWest_LOW(batchId))).xyz;\n' + - 'vec3 southEastCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southEast_HIGH(batchId), czm_batchTable_southEast_LOW(batchId))).xyz;\n'; + 'vec3 northWestCorner = czm_normal * czm_batchTable_northward(batchId) + southWestCorner;\n' + + 'vec3 southEastCorner = czm_normal * czm_batchTable_eastward(batchId) + southWestCorner;\n'; } glsl += 'vec3 eastWard = southEastCorner - southWestCorner;\n' + @@ -545,25 +552,6 @@ define([ } }); - var encodeScratch = new EncodedCartesian3(); - function addAttributesForPoint(point, name, attributes) { - var encoded = EncodedCartesian3.fromCartesian(point, encodeScratch); - - attributes[name + '_HIGH'] = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(encoded.high, [0, 0, 0]) - }); - - attributes[name + '_LOW'] = new GeometryInstanceAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - normalize: false, - value : Cartesian3.pack(encoded.low, [0, 0, 0]) - }); - } - var cartographicScratch = new Cartographic(); var rectangleCenterScratch = new Cartographic(); var northCenterScratch = new Cartesian3(); @@ -712,6 +700,100 @@ define([ }); } + var enuMatrixScratch = new Matrix4(); + var inverseEnuScratch = new Matrix4(); + var rectanglePointCartesianScratch = new Cartesian3(); + 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, southWestCornerResult, eastVectorResult, northVectorResult) { + // Compute center of rectangle + var centerCartographic = Rectangle.center(rectangle, rectangleCenterScratch); + 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++) { + 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. @@ -737,28 +819,40 @@ define([ Check.typeOf.object('projection', projection); //>>includeEnd('debug'); - // Compute corner positions in double precision - var carto = cartographicScratch; - carto.height = 0.0; - - carto.longitude = rectangle.west; - carto.latitude = rectangle.south; - - var corner = Cartographic.toCartesian(carto, ellipsoid, cornerScratch); - - carto.latitude = rectangle.north; - var northWest = Cartographic.toCartesian(carto, ellipsoid, northWestScratch); - - carto.longitude = rectangle.east; - carto.latitude = rectangle.south; - var southEast = Cartographic.toCartesian(carto, ellipsoid, southEastScratch); + var corner = cornerScratch; + var eastward = eastwardScratch; + var northward = northwardScratch; + computeRectangleBounds(rectangle, ellipsoid, corner, eastward, northward); var attributes = { stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) }; - addAttributesForPoint(corner, 'southWest', attributes); - addAttributesForPoint(northWest, 'northWest', attributes); - addAttributesForPoint(southEast, 'southEast', attributes); + + var 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(rectangle, projection, attributes); return attributes; @@ -843,8 +937,7 @@ define([ ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function(attributes) { return defined(attributes.southWest_HIGH) && defined(attributes.southWest_LOW) && - defined(attributes.northWest_HIGH) && defined(attributes.northWest_LOW) && - defined(attributes.southEast_HIGH) && defined(attributes.southEast_LOW) && + defined(attributes.northward) && defined(attributes.eastward) && defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && defined(attributes.stSineCosineUVScale); }; diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index 8b0ea6ef8ea..caf113ef30b 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -1,22 +1,28 @@ defineSuite([ 'Scene/ShadowVolumeAppearance', + 'Core/Cartesian3', 'Core/Cartographic', 'Core/Math', 'Core/ComponentDatatype', 'Core/Ellipsoid', + 'Core/Matrix4', 'Core/WebMercatorProjection', 'Core/Rectangle', + 'Core/Transforms', 'Scene/Material', 'Scene/MaterialAppearance', 'Scene/PerInstanceColorAppearance' ], function( ShadowVolumeAppearance, + Cartesian3, Cartographic, CesiumMath, ComponentDatatype, Ellipsoid, + Matrix4, WebMercatorProjection, Rectangle, + Transforms, Material, MaterialAppearance, PerInstanceColorAppearance) { @@ -77,51 +83,45 @@ defineSuite([ expect(value[3]).toEqualEpsilon(1.0 / CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON4); }); - function checkGeometryInstanceAttributePoint(attribute) { + function checkGeometryInstanceAttributeVec3(attribute) { expect(attribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); expect(attribute.componentsPerAttribute).toEqual(3); expect(attribute.normalize).toEqual(false); } - function checkPointLow(value, cosineAngle) { - // Basically a dot product with UNIT_X. - // Cosines of angles get smaller from 0 to 90 degrees, so this is a GreaterThan check. - expect(value[0]).toBeGreaterThan(cosineAngle); - } - - function checkPointHigh(value) { - expect(value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(value[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - expect(value[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7); - } - it('provides attributes for computing texture coordinates using planes in 3D', function() { var attributes = smallRectangleAttributes; var southWest_LOW = attributes.southWest_LOW; - var northWest_LOW = attributes.northWest_LOW; - var southEast_LOW = attributes.southEast_LOW; var southWest_HIGH = attributes.southWest_HIGH; - var northWest_HIGH = attributes.northWest_HIGH; - var southEast_HIGH = attributes.southEast_HIGH; + var eastward = attributes.eastward; + var northward = attributes.northward; - checkGeometryInstanceAttributePoint(southWest_LOW); - checkGeometryInstanceAttributePoint(northWest_LOW); - checkGeometryInstanceAttributePoint(southEast_LOW); - checkGeometryInstanceAttributePoint(southWest_HIGH); - checkGeometryInstanceAttributePoint(northWest_HIGH); - checkGeometryInstanceAttributePoint(southEast_HIGH); + 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 values to be within a small cone around UNIT_X - checkPointHigh(southWest_HIGH.value); - checkPointHigh(northWest_HIGH.value); - checkPointHigh(southEast_HIGH.value); - - var cosineAngle = Math.cos(CesiumMath.toRadians(0.2)); - checkPointLow(southWest_LOW.value, cosineAngle); - checkPointLow(northWest_LOW.value, cosineAngle); - checkPointLow(southEast_LOW.value, cosineAngle); + // 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() { From 4a00da4845358d5887f93109b2064b1e0f2e0b50 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Sun, 29 Apr 2018 19:11:42 -0400 Subject: [PATCH 25/40] factor height into GroundPrimitive texcoord planes --- Source/Scene/GroundPrimitive.js | 2 +- Source/Scene/ShadowVolumeAppearance.js | 9 ++++++--- Specs/Scene/ShadowVolumeAppearanceSpec.js | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 5c0fbaaab9c..c6e271de9cd 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -816,7 +816,7 @@ define([ var attributes; if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, this._maxHeight, geometry._stRotation); } else { attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); } diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index fd4a19f6c9d..6f38b4fa74d 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -720,9 +720,10 @@ define([ * into the plane of the local ENU system, compute bounds in 2D, and * project back to ellipsoid-centered. */ - function computeRectangleBounds(rectangle, ellipsoid, southWestCornerResult, eastVectorResult, northVectorResult) { + 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); @@ -759,6 +760,7 @@ define([ 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 @@ -809,10 +811,11 @@ define([ * @param {Rectangle} rectangle Rectangle object that the points will approximately bound * @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. * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, projection, textureCoordinateRotation) { + ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, projection, height, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('rectangle', rectangle); Check.typeOf.object('ellipsoid', ellipsoid); @@ -822,7 +825,7 @@ define([ var corner = cornerScratch; var eastward = eastwardScratch; var northward = northwardScratch; - computeRectangleBounds(rectangle, ellipsoid, corner, eastward, northward); + computeRectangleBounds(rectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); var attributes = { stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index caf113ef30b..ade4d308560 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -169,7 +169,7 @@ defineSuite([ }); it('provides attributes for rotating texture coordinates', function() { - var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, CesiumMath.PI_OVER_TWO); + var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, 0.0, CesiumMath.PI_OVER_TWO); var stRotationAttribute = attributes.stSineCosineUVScale; expect(stRotationAttribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); @@ -182,7 +182,7 @@ defineSuite([ expect(value[2]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); // 90 degree rotation of a square, so no scale expect(value[3]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, CesiumMath.PI_OVER_FOUR); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, 0.0, CesiumMath.PI_OVER_FOUR); value = attributes.stSineCosineUVScale.value; expect(value[0]).toEqualEpsilon(Math.sin(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); expect(value[1]).toEqualEpsilon(Math.cos(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); From d13d97fb5f6088d6c7e9293e82403d9afc83a33d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 2 May 2018 11:10:38 -0400 Subject: [PATCH 26/40] PR comments --- Source/Core/Math.js | 15 +- .../StaticGroundGeometryPerMaterialBatch.js | 18 +- Source/Renderer/AutomaticUniforms.js | 6 +- Source/Renderer/UniformState.js | 36 +- Source/Scene/ClassificationPrimitive.js | 180 ++++--- Source/Scene/GroundPrimitive.js | 53 +- Source/Scene/Scene.js | 12 +- Source/Scene/ShadowVolumeAppearance.js | 481 +++++------------- Source/Scene/Vector3DTilePrimitive.js | 13 +- .../Builtin/Functions/planeDistance.glsl | 13 + .../Functions/windowToEyeCoordinates.glsl | 37 ++ Source/Shaders/ShadowVolumeAppearanceFS.glsl | 143 ++++++ Source/Shaders/ShadowVolumeAppearanceVS.glsl | 76 +++ Source/Shaders/ShadowVolumeVS.glsl | 34 -- Source/Shaders/VectorTileVS.glsl | 9 + Specs/Core/MathSpec.js | 6 + Specs/Renderer/BuiltinFunctionsSpec.js | 31 ++ Specs/Scene/SceneSpec.js | 6 - Specs/Scene/ShadowVolumeAppearanceSpec.js | 212 +++++--- 19 files changed, 779 insertions(+), 602 deletions(-) create mode 100644 Source/Shaders/Builtin/Functions/planeDistance.glsl create mode 100644 Source/Shaders/ShadowVolumeAppearanceFS.glsl create mode 100644 Source/Shaders/ShadowVolumeAppearanceVS.glsl delete mode 100644 Source/Shaders/ShadowVolumeVS.glsl create mode 100644 Source/Shaders/VectorTileVS.glsl diff --git a/Source/Core/Math.js b/Source/Core/Math.js index c0be441b798..794b1453c72 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -894,14 +894,21 @@ define([ //>>includeEnd('debug'); // atan approximations are usually only reliable over [-1, 1] - // So reduce the range by flipping whether x or y is on top. - var opposite, adjacent, t; // t used as swap and atan result. - t = Math.abs(x); + // So reduce the range by flipping whether x or y is on top based on which is bigger. + var opposite; + var adjacent; + var t = Math.abs(x); // t used as swap and atan result. opposite = Math.abs(y); adjacent = Math.max(t, opposite); opposite = Math.min(t, opposite); - t = CesiumMath.fastApproximateAtan(opposite / adjacent); + var oppositeOverAdjacent = opposite / adjacent; + //>>includeStart('debug', pragmas.debug); + if (isNaN(oppositeOverAdjacent)) { + throw new DeveloperError('either x or y must be nonzero'); + } + //>>includeEnd('debug'); + t = CesiumMath.fastApproximateAtan(oppositeOverAdjacent); // Undo range reduction t = Math.abs(y) > Math.abs(x) ? CesiumMath.PI_OVER_TWO - t : t; diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index 1cc9a724eef..d01d64aea3a 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -57,8 +57,8 @@ define([ this.invalidated = true; }; - Batch.prototype.nonOverlapping = function(rectangle) { - return !this.rectangleCollisionCheck.collides(rectangle); + Batch.prototype.overlapping = function(rectangle) { + return this.rectangleCollisionCheck.collides(rectangle); }; // Check if the given updater's material is compatible with this batch @@ -152,9 +152,8 @@ define([ asynchronous : true, geometryInstances : geometries, appearance : new this.appearanceType({ - material : this.material, - translucent : this.material.isTranslucent(), - closed : this.closed + material : this.material + // translucent and closed properties overridden }), classificationType : this.classificationType }); @@ -291,15 +290,20 @@ define([ var length = items.length; var geometryInstance = updater.createFillGeometryInstance(time); var usingSphericalTextureCoordinates = ShadowVolumeAppearance.shouldUseSphericalCoordinates(geometryInstance.geometry.rectangle); + // Check if the Entity represented by the updater can be placed in an existing batch. Requirements: + // * compatible material (same material or same color) + // * same type of texture coordinates (spherical vs. planar) + // * conservatively non-overlapping with any entities in the existing batch for (var i = 0; i < length; ++i) { var item = items[i]; if (item.isMaterial(updater) && - item.nonOverlapping(geometryInstance.geometry.rectangle) && + !item.overlapping(geometryInstance.geometry.rectangle) && item.usingSphericalTextureCoordinates === usingSphericalTextureCoordinates) { item.add(time, updater, geometryInstance); return; } } + // If a compatible batch wasn't found, create a new batch. var batch = new Batch(this._primitives, this._appearanceType, this._classificationType, updater.fillMaterialProperty, usingSphericalTextureCoordinates); batch.add(time, updater, geometryInstance); items.push(batch); @@ -350,7 +354,7 @@ define([ var length = items.length; for (var i = 0; i < length; i++) { var item = items[i]; - if(item.contains(updater)){ + if (item.contains(updater)){ return item.getBoundingSphere(updater, result); } } diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 4619017dc20..55fd1836622 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1149,7 +1149,7 @@ define([ size : 1, datatype : WebGLConstants.FLOAT, getValue : function(uniformState) { - return uniformState.logFarDistance; + return uniformState.log2FarDistance; } }), @@ -1164,7 +1164,7 @@ define([ size : 1, datatype : WebGLConstants.FLOAT, getValue : function(uniformState) { - return CesiumMath.log2(uniformState.currentFrustum.y + 1.0); + return uniformState.log2FarPlusOne; } }), @@ -1179,7 +1179,7 @@ define([ size : 1, datatype : WebGLConstants.FLOAT, getValue : function(uniformState) { - return CesiumMath.log2(uniformState.currentFrustum.x); + return uniformState.log2NearDistance; } }), diff --git a/Source/Renderer/UniformState.js b/Source/Renderer/UniformState.js index 1c6d1a405a2..0097e65d3dd 100644 --- a/Source/Renderer/UniformState.js +++ b/Source/Renderer/UniformState.js @@ -61,7 +61,9 @@ define([ this._entireFrustum = new Cartesian2(); this._currentFrustum = new Cartesian2(); this._frustumPlanes = new Cartesian4(); - this._logFarDistance = undefined; + this._log2FarDistance = undefined; + this._log2FarPlusOne = undefined; + this._log2NearDistance = undefined; this._frameState = undefined; this._temeToPseudoFixed = Matrix3.clone(Matrix4.IDENTITY); @@ -643,13 +645,35 @@ define([ }, /** - * The log of the current frustum's far distance. Used to compute the log depth. + * The log2 of the current frustum's far distance. Used to compute the log depth. * @memberof UniformState.prototype * @type {Number} */ - logFarDistance : { + log2FarDistance : { get : function() { - return this._logFarDistance; + return this._log2FarDistance; + } + }, + + /** + * The log2 of 1 + the current frustum's far distance. Used to reverse log depth. + * @memberof UniformState.prototype + * @type {Number} + */ + log2FarPlusOne : { + get : function() { + return this._log2FarPlusOne; + } + }, + + /** + * The log2 current frustum's near distance. Used when writing log depth in the fragment shader. + * @memberof UniformState.prototype + * @type {Number} + */ + log2NearDistance : { + get : function() { + return this._log2NearDistance; } }, @@ -999,7 +1023,9 @@ define([ this._currentFrustum.x = frustum.near; this._currentFrustum.y = frustum.far; - this._logFarDistance = 2.0 / CesiumMath.log2(frustum.far + 1.0); + this._log2FarDistance = 2.0 / CesiumMath.log2(frustum.far + 1.0); + this._log2FarPlusOne = CesiumMath.log2(frustum.far + 1.0); + this._log2NearDistance = CesiumMath.log2(frustum.near); if (defined(frustum._offCenterFrustum)) { frustum = frustum._offCenterFrustum; diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index 6c0656321b0..df09e3bf69b 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -14,7 +14,7 @@ define([ '../Renderer/ShaderProgram', '../Renderer/ShaderSource', '../Shaders/ShadowVolumeFS', - '../Shaders/ShadowVolumeVS', + '../Shaders/ShadowVolumeAppearanceVS', '../ThirdParty/when', './BlendingState', './ClassificationType', @@ -41,7 +41,7 @@ define([ ShaderProgram, ShaderSource, ShadowVolumeFS, - ShadowVolumeVS, + ShadowVolumeAppearanceVS, when, BlendingState, ClassificationType, @@ -173,9 +173,10 @@ define([ this._sp = undefined; this._spStencil = undefined; this._spPick = undefined; - this._spPick2D = undefined; this._spColor = undefined; - this._spColor2D = undefined; + + this._spPick2D = undefined; // only derived if necessary + this._spColor2D = undefined; // only derived if necessary this._rsStencilPreloadPass = undefined; this._rsStencilDepthPass = undefined; @@ -347,6 +348,21 @@ define([ get : function() { return this._readyPromise.promise; } + }, + + /** + * Returns true if the ClassificationPrimitive needs a separate shader and commands for 2D. + * This is because texture coordinates on ClassificationPrimitives are computed differently, + * and are used for culling when multiple GeometryInstances are batched in one ClassificationPrimitive. + * @memberof ClassificationPrimitive.prototype + * @type {Boolean} + * @readonly + * @private + */ + _needs2DShader : { + get : function() { + return this._hasPlanarExtentsAttributes || this._hasSphericalExtentsAttribute; + } } }); @@ -525,14 +541,14 @@ define([ function createShaderProgram(classificationPrimitive, frameState) { var context = frameState.context; var primitive = classificationPrimitive._primitive; - var vs = ShadowVolumeVS; + var vs = ShadowVolumeAppearanceVS; vs = classificationPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); vs = Primitive._updateColorAttribute(primitive, vs); var planarExtents = classificationPrimitive._hasPlanarExtentsAttributes; - var cullUsingExtents = planarExtents || classificationPrimitive._hasSphericalExtentsAttribute; + var cullFragmentsUsingExtents = planarExtents || classificationPrimitive._hasSphericalExtentsAttribute; if (classificationPrimitive._extruded) { vs = modifyForEncodedNormals(primitive, vs); @@ -553,7 +569,7 @@ define([ }); var attributeLocations = classificationPrimitive._primitive._attributeLocations; - var shadowVolumeAppearance = new ShadowVolumeAppearance(cullUsingExtents, planarExtents, classificationPrimitive.appearance); + var shadowVolumeAppearance = new ShadowVolumeAppearance(cullFragmentsUsingExtents, planarExtents, classificationPrimitive.appearance); classificationPrimitive._spStencil = ShaderProgram.replaceCache({ context : context, @@ -568,15 +584,8 @@ define([ vsPick = Primitive._appendShowToShader(primitive, vsPick); vsPick = Primitive._updatePickColorAttribute(vsPick); - var pickFS3D = new ShaderSource({ - sources : [shadowVolumeAppearance.createPickingFragmentShader(false)], - pickColorQualifier : 'varying' - }); - - var pickVS3D = new ShaderSource({ - defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearance.createVertexShader(vsPick, false)] - }); + var pickFS3D = shadowVolumeAppearance.createPickFragmentShader(false); + var pickVS3D = shadowVolumeAppearance.createPickVertexShader([extrudedDefine, disableGlPositionLogDepth], vsPick, false); classificationPrimitive._spPick = ShaderProgram.replaceCache({ context : context, @@ -586,23 +595,22 @@ define([ attributeLocations : attributeLocations }); - var pickFS2D = new ShaderSource({ - sources : [shadowVolumeAppearance.createPickingFragmentShader(true)], - pickColorQualifier : 'varying' - }); - - var pickVS2D = new ShaderSource({ - defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearance.createVertexShader(vsPick, true)] - }); - - classificationPrimitive._spPick2D = ShaderProgram.replaceCache({ - context : context, - shaderProgram : classificationPrimitive._spPick2D, - vertexShaderSource : pickVS2D, - fragmentShaderSource : pickFS2D, - attributeLocations : attributeLocations - }); + // Derive a 2D pick shader if the primitive uses texture coordinate-based fragment culling, + // since texture coordinates are computed differently in 2D. + if (cullFragmentsUsingExtents) { + var pickProgram2D = context.shaderCache.getDerivedShaderProgram(classificationPrimitive._spPick, '2dPick'); + if (!defined(pickProgram2D)) { + var pickFS2D = shadowVolumeAppearance.createPickFragmentShader(true); + var pickVS2D = shadowVolumeAppearance.createPickVertexShader([extrudedDefine, disableGlPositionLogDepth], vsPick, true); + + pickProgram2D = context.shaderCache.createDerivedShaderProgram(classificationPrimitive._spPick, '2dPick', { + vertexShaderSource : pickVS2D, + fragmentShaderSource : pickFS2D, + attributeLocations : attributeLocations + }); + } + classificationPrimitive._spPick2D = pickProgram2D; + } } else { classificationPrimitive._spPick = ShaderProgram.fromCache({ context : context, @@ -627,14 +635,8 @@ define([ }); // Create a fragment shader that computes only required material hookups using screen space techniques - var fsColorSource = new ShaderSource({ - sources : [shadowVolumeAppearance.createAppearanceFragmentShader(false)] - }); - - var vsColorSource = new ShaderSource({ - defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearance.createVertexShader(vs, false)] - }); + var fsColorSource = shadowVolumeAppearance.createFragmentShader(false); + var vsColorSource = shadowVolumeAppearance.createVertexShader([extrudedDefine, disableGlPositionLogDepth], vs, false); classificationPrimitive._spColor = ShaderProgram.replaceCache({ context : context, @@ -644,23 +646,23 @@ define([ attributeLocations : attributeLocations }); - // Create a similar fragment shader for 2D, forcing planar extents - var fsColorSource2D = new ShaderSource({ - sources : [shadowVolumeAppearance.createAppearanceFragmentShader(true)] - }); - - var vsColorSource2D = new ShaderSource({ - defines : [extrudedDefine, disableGlPositionLogDepth], - sources : [shadowVolumeAppearance.createVertexShader(vs, true)] - }); - - classificationPrimitive._spColor2D = ShaderProgram.replaceCache({ - context : context, - shaderProgram : classificationPrimitive._spColor2D, - vertexShaderSource : vsColorSource2D, - fragmentShaderSource : fsColorSource2D, - attributeLocations : attributeLocations - }); + // Derive a 2D shader if the primitive uses texture coordinate-based fragment culling, + // since texture coordinates are computed differently in 2D. + // Any material that uses texture coordinates will also equip texture coordinate-based fragment culling. + if (cullFragmentsUsingExtents) { + var colorProgram2D = context.shaderCache.getDerivedShaderProgram(classificationPrimitive._spColor, '2dColor'); + if (!defined(colorProgram2D)) { + var fsColorSource2D = shadowVolumeAppearance.createFragmentShader(true); + var vsColorSource2D = shadowVolumeAppearance.createVertexShader([extrudedDefine, disableGlPositionLogDepth], vs, true); + + colorProgram2D = context.shaderCache.createDerivedShaderProgram(classificationPrimitive._spColor, '2dColor', { + vertexShaderSource : vsColorSource2D, + fragmentShaderSource : fsColorSource2D, + attributeLocations : attributeLocations + }); + } + classificationPrimitive._spColor2D = colorProgram2D; + } } function createColorCommands(classificationPrimitive, colorCommands) { @@ -673,6 +675,8 @@ define([ var vaIndex = 0; var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + var needs2DShader = classificationPrimitive._needs2DShader; + for (i = 0; i < length; i += 3) { var vertexArray = primitive._va[vaIndex++]; @@ -724,6 +728,19 @@ define([ } command.uniformMap = uniformMap; + + // derive for 2D if texture coordinates are ever computed + if (needs2DShader) { + var derivedColorCommand = command.derivedCommands.appearance2D; + if (!defined(derivedColorCommand)) { + derivedColorCommand = DrawCommand.shallowClone(command); + command.derivedCommands.appearance2D = derivedColorCommand; + } + derivedColorCommand.vertexArray = vertexArray; + derivedColorCommand.renderState = classificationPrimitive._rsColorPass; + derivedColorCommand.shaderProgram = classificationPrimitive._spColor2D; + derivedColorCommand.uniformMap = uniformMap; + } } var commandsIgnoreShow = classificationPrimitive._commandsIgnoreShow; @@ -755,6 +772,8 @@ define([ var vaIndex = 0; var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + var needs2DShader = classificationPrimitive._needs2DShader; + for (j = 0; j < length; j += 3) { var vertexArray = primitive._va[vaIndex++]; @@ -799,6 +818,19 @@ define([ command.renderState = classificationPrimitive._rsPickPass; command.shaderProgram = classificationPrimitive._spPick; command.uniformMap = uniformMap; + + // derive for 2D if texture coordinates are ever computed + if (needs2DShader) { + var derivedPickCommand = command.derivedCommands.pick2D; + if (!defined(derivedPickCommand)) { + derivedPickCommand = DrawCommand.shallowClone(command); + command.derivedCommands.pick2D = derivedPickCommand; + } + derivedPickCommand.vertexArray = vertexArray; + derivedPickCommand.renderState = classificationPrimitive._rsPickPass; + derivedPickCommand.shaderProgram = classificationPrimitive._spPick2D; + derivedPickCommand.uniformMap = uniformMap; + } } } @@ -920,31 +952,31 @@ define([ var i; var instance; + var attributes; var hasPerColorAttribute = false; var hasSphericalExtentsAttribute = false; var hasPlanarExtentsAttributes = false; + if (length > 0) { + attributes = instances[0].attributes; + // Not expecting these to be set by users, should only be set via GroundPrimitive. + // So don't check for mismatch. + hasSphericalExtentsAttribute = ShadowVolumeAppearance.hasAttributesForSphericalExtents(attributes); + hasPlanarExtentsAttributes = ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes); + } + for (i = 0; i < length; i++) { instance = instances[i]; - var attributes = instance.attributes; + attributes = instance.attributes; if (defined(attributes.color)) { hasPerColorAttribute = true; } //>>includeStart('debug', pragmas.debug); else if (hasPerColorAttribute) { - throw new DeveloperError('All GeometryInstances must have the same attributes.'); + throw new DeveloperError('All GeometryInstances must have color attributes to use per-instance color.'); } //>>includeEnd('debug'); - - // Not expecting these to be set by users, should only be set via GroundPrimitive. - // So don't check for mismatch. - if (ShadowVolumeAppearance.hasAttributesForSphericalExtents(attributes)) { - hasSphericalExtentsAttribute = true; - } - if (ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes)) { - hasPlanarExtentsAttributes = true; - } } // default to a color appearance @@ -957,10 +989,10 @@ define([ //>>includeStart('debug', pragmas.debug); if (!hasPerColorAttribute && appearance instanceof PerInstanceColorAppearance) { - throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttribute'); + throw new DeveloperError('PerInstanceColorAppearance requires color GeometryInstanceAttributes on all GeometryInstances'); } if (defined(appearance.material) && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { - throw new DeveloperError('Materials on ClassificationPrimitives are not supported except via GroundPrimitive'); + throw new DeveloperError('Materials on ClassificationPrimitives are not supported except via GroundPrimitives'); } //>>includeEnd('debug'); @@ -1113,9 +1145,11 @@ define([ this._primitive = this._primitive && this._primitive.destroy(); this._sp = this._sp && this._sp.destroy(); this._spPick = this._spPick && this._spPick.destroy(); - this._spPick2D = this._spPick2D && this._spPick2D.destroy(); this._spColor = this._spColor && this._spColor.destroy(); - this._spColor2D = this._spColor2D && this._spColor2D.destroy(); + + // Derived programs, destroyed above if they existed. + this._spPick2D = undefined; + this._spColor2D = undefined; return destroyObject(this); }; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index c6e271de9cd..a4b83f96d3c 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -52,6 +52,12 @@ define([ ShadowVolumeAppearance) { 'use strict'; + var GroundPrimitiveUniformMap = { + u_globeMinimumAltitude: function() { + return 55000.0; + } + }; + /** * A ground primitive represents geometry draped over the terrain in the {@link Scene}. *

@@ -234,13 +240,6 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; - var uniformMap = { - u_globeMinimumAltitude: function() { - return 55000.0; - } - }; - this._uniformMap = uniformMap; - var that = this; this._classificationPrimitiveOptions = { geometryInstances : undefined, @@ -255,7 +254,7 @@ define([ _updateAndQueueCommandsFunction : undefined, _pickPrimitive : that, _extruded : true, - _uniformMap : uniformMap + _uniformMap : GroundPrimitiveUniformMap }; } @@ -630,6 +629,14 @@ define([ for (i = 0; i < colorLength; ++i) { colorCommand = colorCommands[i]; + + // derive a separate appearance command for 2D if needed + if (frameState.mode !== SceneMode.SCENE3D && + colorCommand.shaderProgram === classificationPrimitive._spColor && + classificationPrimitive._needs2DShader) { + colorCommand = colorCommand.derivedCommands.appearance2D; + } + colorCommand.owner = groundPrimitive; colorCommand.modelMatrix = modelMatrix; colorCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(i, colorLength)]; @@ -637,18 +644,6 @@ define([ colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; colorCommand.pass = pass; - // derive a separate appearance command for 2D - if (frameState.mode !== SceneMode.SCENE3D && - colorCommand.shaderProgram === classificationPrimitive._spColor) { - var derivedColorCommand = colorCommand.derivedCommands.appearance2D; - if (!defined(derivedColorCommand)) { - derivedColorCommand = DrawCommand.shallowClone(colorCommand); - derivedColorCommand.shaderProgram = classificationPrimitive._spColor2D; - colorCommand.derivedCommands.appearance2D = derivedColorCommand; - } - colorCommand = derivedColorCommand; - } - commandList.push(colorCommand); } @@ -674,23 +669,19 @@ define([ for (var j = 0; j < pickLength; ++j) { var pickCommand = pickCommands[j]; + // derive a separate appearance command for 2D if needed + if (frameState.mode !== SceneMode.SCENE3D && + pickCommand.shaderProgram === classificationPrimitive._spPick && + classificationPrimitive._needs2DShader) { + pickCommand = pickCommand.derivedCommands.pick2D; + } + pickCommand.owner = groundPrimitive; pickCommand.modelMatrix = modelMatrix; pickCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(j, pickLength)]; pickCommand.cull = cull; pickCommand.pass = pass; - // derive a separate appearance command for 2D - if (frameState.mode !== SceneMode.SCENE3D && - pickCommand.shaderProgram === classificationPrimitive._spPick) { - var derivedPickCommand = pickCommand.derivedCommands.pick2D; - if (!defined(derivedPickCommand)) { - derivedPickCommand = DrawCommand.shallowClone(pickCommand); - derivedPickCommand.shaderProgram = classificationPrimitive._spPick2D; - pickCommand.derivedCommands.pick2D = derivedPickCommand; - } - pickCommand = derivedPickCommand; - } commandList.push(pickCommand); } } diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 776143d5827..61c0c42ed51 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -644,16 +644,6 @@ define([ */ this.cameraEventWaitTime = 500.0; - /** - * Set to true to copy the depth texture after rendering the globe. Makes czm_globeDepthTexture valid. - * Set to false if Entities on terrain or GroundPrimitives are not used for a potential performance improvement. - * - * @type {Boolean} - * @default true - * @private - */ - this.copyGlobeDepth = true; - /** * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional * performance improvements by rendering less geometry and dispatching less terrain requests. @@ -2199,7 +2189,7 @@ define([ executeCommand(commands[j], scene, context, passState); } - if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && (scene.copyGlobeDepth || scene.debugShowGlobeDepth)) { + if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { globeDepth.update(context, passState); globeDepth.executeCopyDepth(context, passState); } diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 6f38b4fa74d..1493104525c 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -15,7 +15,8 @@ define([ '../Core/Rectangle', '../Core/Transforms', '../Renderer/ShaderSource', - '../Scene/PerInstanceColorAppearance' + '../Scene/PerInstanceColorAppearance', + '../Shaders/ShadowVolumeAppearanceFS' ], function( Cartographic, Cartesian2, @@ -33,7 +34,8 @@ define([ Rectangle, Transforms, ShaderSource, - PerInstanceColorAppearance) { + PerInstanceColorAppearance, + ShadowVolumeAppearanceFS) { 'use strict'; /** @@ -52,24 +54,28 @@ define([ //>>includeEnd('debug'); // Compute shader dependencies - var shaderDependencies = new ShaderDependencies(); - shaderDependencies.requiresTextureCoordinates = extentsCulling; - shaderDependencies.requiresEC = !appearance.flat; + 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 - shaderDependencies.requiresNormalEC = !appearance.flat; + 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; - shaderDependencies.normalEC = materialShaderSource.includes('materialInput.normalEC') || materialShaderSource.includes('czm_getDefaultMaterial'); - shaderDependencies.positionToEyeEC = materialShaderSource.includes('materialInput.positionToEyeEC'); - shaderDependencies.tangentToEyeMatrix = materialShaderSource.includes('materialInput.tangentToEyeMatrix'); - shaderDependencies.st = materialShaderSource.includes('materialInput.st'); + 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._shaderDependencies = shaderDependencies; + this._colorShaderDependencies = colorShaderDependencies; + this._pickShaderDependencies = pickShaderDependencies; this._appearance = appearance; this._extentsCulling = extentsCulling; this._planarExtents = planarExtents; @@ -79,372 +85,154 @@ define([ * 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 {String} Shader source for the fragment shader including its material. + * @returns {ShaderSource} Shader source for the fragment shader. */ - ShadowVolumeAppearance.prototype.createAppearanceFragmentShader = function(columbusView2D) { + ShadowVolumeAppearance.prototype.createFragmentShader = function(columbusView2D) { //>>includeStart('debug', pragmas.debug); Check.typeOf.bool('columbusView2D', columbusView2D); //>>includeEnd('debug'); var appearance = this._appearance; - var materialHookups = createShadowVolumeAppearanceFS(this._shaderDependencies, appearance, this._extentsCulling, this._planarExtents || columbusView2D); - if (appearance instanceof PerInstanceColorAppearance) { - return materialHookups; - } - return appearance.material.shaderSource + '\n' + materialHookups; - }; + var dependencies = this._colorShaderDependencies; - /** - * Create the fragment shader for a ClassificationPrimitive's color pass when rendering for pick. - * - * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. - * @returns {String} Shader source for the fragment shader. - */ - ShadowVolumeAppearance.prototype.createPickingFragmentShader = function(columbusView2D) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.bool('columbusView2D', columbusView2D); - //>>includeEnd('debug'); - - return getPickShaderFS(this._extentsCulling, this._planarExtents || columbusView2D); - }; - - /** - * Create the vertex shader for a ClassificationPrimitive's color pass, both when rendering for color and for pick. - * - * @param {String} vertexShaderSource Vertex shader source. - * @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D. - * @returns {String} Shader source for the vertex shader. - */ - ShadowVolumeAppearance.prototype.createVertexShader = function(vertexShaderSource, columbusView2D) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.string('vertexShaderSource', vertexShaderSource); - Check.typeOf.bool('columbusView2D', columbusView2D); - //>>includeEnd('debug'); - - return createShadowVolumeAppearanceVS(this._shaderDependencies, this._appearance, this._planarExtents, columbusView2D, vertexShaderSource); - }; - - function createShadowVolumeAppearanceFS(shaderDependencies, appearance, extentsCull, planarExtents) { - if (appearance instanceof PerInstanceColorAppearance) { - return getPerInstanceColorShaderFS(shaderDependencies, extentsCull, appearance.flat, planarExtents); + var defines = []; + if (!columbusView2D && !this._planarExtents) { + defines.push('SPHERICAL'); } - - var usesNormalEC = shaderDependencies.normalEC; - var usesPositionToEyeEC = shaderDependencies.positionToEyeEC; - var usesTangentToEyeMat = shaderDependencies.tangentToEyeMatrix; - var usesSt = shaderDependencies.st; - - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n'; - if (extentsCull || usesSt) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; + if (dependencies.requiresEC) { + defines.push('REQUIRES_EC'); } - if (usesSt) { - glsl += - 'varying vec4 v_stSineCosineUVScale;\n'; + if (dependencies.requiresWC) { + defines.push('REQUIRES_WC'); } - - // Get local functions - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); - - glsl += - 'void main(void)\n' + - '{\n'; - - // Compute material input stuff and cull if outside texture coordinate extents - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCull, planarExtents); - - glsl += ' czm_materialInput materialInput;\n'; - if (usesNormalEC) { - glsl += ' materialInput.normalEC = normalEC;\n'; + if (dependencies.requiresTextureCoordinates) { + defines.push('TEXTURE_COORDINATES'); } - if (usesPositionToEyeEC) { - glsl += ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n'; + if (this._extentsCulling) { + defines.push('CULL_FRAGMENTS'); } - if (usesTangentToEyeMat) { - glsl += ' materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC);\n'; + if (dependencies.requiresNormalEC) { + defines.push('NORMAL_EC'); } - if (usesSt) { - // Scale texture coordinates and rotate around 0.5, 0.5 - glsl += - ' materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5;\n' + - ' materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5;\n'; + if (appearance instanceof PerInstanceColorAppearance) { + defines.push('PER_INSTANCE_COLOR'); } - glsl += ' czm_material material = czm_getMaterial(materialInput);\n'; - if (appearance.flat) { - glsl += ' gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n'; - } else { - glsl += ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; + // 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'); } - glsl += ' czm_writeDepthClampedToFarPlane();\n'; - glsl += '}\n'; - return glsl; - } - - var pickingShaderDependenciesScratch = new ShaderDependencies(); - function getPickShaderFS(extentsCulling, planarExtents) { - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n'; - if (extentsCulling) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; + 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'); } - var shaderDependencies = pickingShaderDependenciesScratch; - shaderDependencies.reset(); - shaderDependencies.requiresTextureCoordinates = extentsCulling; - shaderDependencies.requiresNormalEC = false; - - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); - - glsl += 'void main(void)\n' + - '{\n'; - glsl += ' bool culled = false;\n'; - var outOfBoundsSnippet = - ' culled = true;\n'; - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet); - glsl += ' if (!culled) {\n' + - ' gl_FragColor.a = 1.0;\n' + // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource - ' czm_writeDepthClampedToFarPlane();\n' + - ' }\n' + - '}\n'; - return glsl; - } - function getPerInstanceColorShaderFS(shaderDependencies, extentsCulling, flatShading, planarExtents) { - var glsl = - '#ifdef GL_EXT_frag_depth\n' + - '#extension GL_EXT_frag_depth : enable\n' + - '#endif\n' + - 'varying vec4 v_color;\n'; - if (extentsCulling) { - glsl += planarExtents ? - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' : - - 'varying vec4 v_sphericalExtents;\n'; + if (appearance.flat) { + defines.push('FLAT'); } - glsl += getLocalFunctionsFS(shaderDependencies, planarExtents); + var materialSource = ''; + if (!(appearance instanceof PerInstanceColorAppearance)) { + materialSource = appearance.material.shaderSource; + } - glsl += 'void main(void)\n' + - '{\n'; + return new ShaderSource({ + defines : defines, + sources : [materialSource, ShadowVolumeAppearanceFS] + }); + }; - glsl += getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents); + ShadowVolumeAppearance.prototype.createPickFragmentShader = function(columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); - if (flatShading) { - glsl += - ' gl_FragColor = v_color;\n'; - } else { - glsl += - ' czm_materialInput materialInput;\n' + - ' materialInput.normalEC = normalEC;\n' + - ' materialInput.positionToEyeEC = -eyeCoordinate.xyz;\n' + - ' czm_material material = czm_getDefaultMaterial(materialInput);\n' + - ' material.diffuse = v_color.rgb;\n' + - ' material.alpha = v_color.a;\n' + - - ' gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material);\n'; - } - glsl += ' czm_writeDepthClampedToFarPlane();\n'; - glsl += '}\n'; - return glsl; - } + var dependencies = this._pickShaderDependencies; - function getDependenciesAndCullingFS(shaderDependencies, extentsCulling, planarExtents, outOfBoundsSnippet) { - var glsl = ''; - if (shaderDependencies.requiresEC) { - glsl += - ' vec4 eyeCoordinate = getEyeCoordinate(gl_FragCoord.xy);\n'; + var defines = ['PICK']; + if (!columbusView2D && !this._planarExtents) { + defines.push('SPHERICAL'); } - if (shaderDependencies.requiresWC) { - glsl += - ' vec4 worldCoordinate4 = czm_inverseView * eyeCoordinate;\n' + - ' vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;\n'; + if (dependencies.requiresEC) { + defines.push('REQUIRES_EC'); } - if (shaderDependencies.requiresTextureCoordinates) { - if (planarExtents) { - glsl += - ' // Unpack planes and transform to eye space\n' + - ' float u = computePlanarTextureCoordinates(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.y);\n' + - ' float v = computePlanarTextureCoordinates(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w, v_inversePlaneExtents.x);\n'; - } else { - glsl += - ' // Treat world coords as a sphere normal for spherical coordinates\n' + - ' vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate);\n' + - ' float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z;\n' + - ' float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w;\n'; - } + if (dependencies.requiresWC) { + defines.push('REQUIRES_WC'); } - if (extentsCulling) { - if (!defined(outOfBoundsSnippet)) { - outOfBoundsSnippet = - ' discard;\n'; - } - glsl += - ' if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) {\n' + - outOfBoundsSnippet + - ' }\n'; + if (dependencies.requiresTextureCoordinates) { + defines.push('TEXTURE_COORDINATES'); } - // Lots of texture access, so lookup after discard check - if (shaderDependencies.requiresNormalEC) { - glsl += - ' // compute normal. sample adjacent pixels in 2x2 block in screen space\n' + - ' vec3 downUp = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(0.0, 1.0));\n' + - ' vec3 leftRight = getVectorFromOffset(eyeCoordinate, gl_FragCoord.xy, vec2(1.0, 0.0));\n' + - ' vec3 normalEC = normalize(cross(leftRight, downUp));\n' + - '\n'; - } - return glsl; - } - - function getLocalFunctionsFS(shaderDependencies, planarExtents) { - var glsl = ''; - if (shaderDependencies.requiresEC || shaderDependencies.requiresNormalEC) { - glsl += - 'vec4 windowToEyeCoordinates(vec2 xy, float depthOrLogDepth) {\n' + - // See reverseLogDepth.glsl. This is separate to re-use the pow. - '#ifdef LOG_DEPTH\n' + - ' float near = czm_currentFrustum.x;\n' + - ' float far = czm_currentFrustum.y;\n' + - ' float unscaledDepth = pow(2.0, depthOrLogDepth * czm_log2FarPlusOne) - 1.0;\n' + - ' vec4 windowCoord = vec4(xy, far * (1.0 - near / unscaledDepth) / (far - near), 1.0);\n' + - ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + - ' eyeCoordinate.w = 1.0 / unscaledDepth;\n' + // Better precision - '#else\n' + - ' vec4 windowCoord = vec4(xy, depthOrLogDepth, 1.0);\n' + - ' vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);\n' + - '#endif\n' + - ' return eyeCoordinate;\n' + - '}\n'; + if (this._extentsCulling) { + defines.push('CULL_FRAGMENTS'); } - if (shaderDependencies.requiresEC) { - glsl += - 'vec4 getEyeCoordinate(vec2 fragCoord) {\n' + - ' vec2 coords = fragCoord / czm_viewport.zw;\n' + - ' float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));\n' + - ' return windowToEyeCoordinates(fragCoord, logDepthOrDepth);\n' + - '}\n'; - } - if (shaderDependencies.requiresNormalEC) { - glsl += - 'vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) {\n' + - ' vec4 eyeCoordinate = windowToEyeCoordinates(fragCoord, logDepthOrDepth);\n' + - ' return eyeCoordinate.xyz / eyeCoordinate.w;\n' + - '}\n' + - - 'vec3 getVectorFromOffset(vec4 eyeCoordinate, vec2 glFragCoordXY, vec2 positiveOffset) {\n' + - ' // Sample depths at both offset and negative offset\n' + - ' float upOrRightLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY + positiveOffset) / czm_viewport.zw));\n' + - ' float downOrLeftLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw));\n' + - ' // Explicitly evaluate both paths\n' + // Necessary for multifrustum and for GroundPrimitives at the edges of the screen - ' bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw);\n' + - ' float useUpOrRight = float(upOrRightLogDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y);\n' + - ' float useDownOrLeft = float(useUpOrRight == 0.0);\n' + - ' vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY + positiveOffset, upOrRightLogDepth);\n' + - ' vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY - positiveOffset, downOrLeftLogDepth);\n' + - - ' return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft;\n' + - '}\n'; - } - if (shaderDependencies.requiresTextureCoordinates && planarExtents) { - glsl += - 'float computePlanarTextureCoordinates(vec4 plane, vec3 eyeCoordinates, float inverseExtent) {\n' + - ' return (dot(plane.xyz, eyeCoordinates) + plane.w) * inverseExtent;\n' + - '}\n'; - } - return glsl; - } + return new ShaderSource({ + defines : defines, + sources : [ShadowVolumeAppearanceFS], + pickColorQualifier : 'varying' + }); + }; - function createShadowVolumeAppearanceVS(shaderDependencies, appearance, planarExtents, columbusView2D, shadowVolumeVS) { - var glsl = ShaderSource.replaceMain(shadowVolumeVS, 'computePosition'); + /** + * 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. + * @returns {String} Shader source for the vertex shader. + */ + ShadowVolumeAppearance.prototype.createVertexShader = function(defines, vertexShaderSource, columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.defined('defines', defines); + Check.typeOf.string('vertexShaderSource', vertexShaderSource); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); + return createShadowVolumeAppearanceVS(this._colorShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, this._appearance); + }; - var isPerInstanceColor = defined(appearance) && appearance instanceof PerInstanceColorAppearance; - if (isPerInstanceColor) { - glsl += 'varying vec4 v_color;\n'; - } + /** + * 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. + * @returns {String} Shader source for the vertex shader. + */ + ShadowVolumeAppearance.prototype.createPickVertexShader = function(defines, vertexShaderSource, columbusView2D) { + //>>includeStart('debug', pragmas.debug); + Check.defined('defines', defines); + Check.typeOf.string('vertexShaderSource', vertexShaderSource); + Check.typeOf.bool('columbusView2D', columbusView2D); + //>>includeEnd('debug'); + return createShadowVolumeAppearanceVS(this._pickShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource); + }; - var spherical = !(planarExtents || columbusView2D); - if (shaderDependencies.requiresTextureCoordinates) { - if (spherical) { - glsl += - 'varying vec4 v_sphericalExtents;\n' + - 'varying vec4 v_stSineCosineUVScale;\n'; - } else { - glsl += - 'varying vec2 v_inversePlaneExtents;\n' + - 'varying vec4 v_westPlane;\n' + - 'varying vec4 v_southPlane;\n' + - 'varying vec4 v_stSineCosineUVScale;\n'; - } - } + function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance) { + var allDefines = defines.slice(); - glsl += - 'void main()\n' + - '{\n' + - ' computePosition();\n'; - if (isPerInstanceColor) { - glsl += 'v_color = czm_batchTable_color(batchId);\n'; + if (defined(appearance) && appearance instanceof PerInstanceColorAppearance) { + allDefines.push('PER_INSTANCE_COLOR'); } - - // Add code for computing texture coordinate dependencies if (shaderDependencies.requiresTextureCoordinates) { - if (spherical) { - glsl += - 'v_sphericalExtents = czm_batchTable_sphericalExtents(batchId);\n' + - 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; - } else { - // Two varieties of planar texcoords - if (columbusView2D) { - // 2D/CV case may have very large "plane extents," so planes and distances encoded as 3 64 bit positions, - // which in 2D can be encoded as 2 64 bit vec2s - glsl += - 'vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId);\n' + - 'vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId);\n' + - 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz;\n' + - 'vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz;\n' + - '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;\n'; - } else { - glsl += - // 3D case has smaller "plane extents," so planes encoded as a 64 bit position and 2 vec3s for distances/direction - 'vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(czm_batchTable_southWest_HIGH(batchId), czm_batchTable_southWest_LOW(batchId))).xyz;\n' + - 'vec3 northWestCorner = czm_normal * czm_batchTable_northward(batchId) + southWestCorner;\n' + - 'vec3 southEastCorner = czm_normal * czm_batchTable_eastward(batchId) + southWestCorner;\n'; - } - glsl += - 'vec3 eastWard = southEastCorner - southWestCorner;\n' + - 'float eastExtent = length(eastWard);\n' + - 'eastWard /= eastExtent;\n' + - - 'vec3 northWard = northWestCorner - southWestCorner;\n' + - 'float northExtent = length(northWard);\n' + - 'northWard /= northExtent;\n' + - - 'v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner));\n' + - 'v_southPlane = vec4(northWard, -dot(northWard, southWestCorner));\n' + - 'v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent);\n' + - 'v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId);\n'; + allDefines.push('TEXTURE_COORDINATES'); + if (!(planarExtents || columbusView2D)) { + allDefines.push('SPHERICAL'); + } + if (columbusView2D) { + allDefines.push('COLUMBUS_VIEW_2D'); } } - glsl += - '}\n'; - - return glsl; + return new ShaderSource({ + defines : allDefines, + sources : [vertexShaderSource] + }); } /** @@ -463,18 +251,6 @@ define([ this._usesSt = false; } - ShaderDependencies.prototype.reset = function() { - this._requiresEC = false; - this._requiresWC = false; - this._requiresNormalEC = false; - this._requiresTextureCoordinates = false; - - 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 : { @@ -622,7 +398,8 @@ define([ } // Depending on the rotation, east/west may be more appropriate for vertical scale than horizontal - var scaleU, scaleV; + var scaleU = 1.0; + var scaleV = 1.0; if (Math.abs(sinTheta) < Math.abs(cosTheta)) { scaleU = eastWestHalfDistance / ((max2D.x - min2D.x) * 0.5); scaleV = northSouthHalfDistance / ((max2D.y - min2D.y) * 0.5); diff --git a/Source/Scene/Vector3DTilePrimitive.js b/Source/Scene/Vector3DTilePrimitive.js index 112035c9110..b9cbb67804b 100644 --- a/Source/Scene/Vector3DTilePrimitive.js +++ b/Source/Scene/Vector3DTilePrimitive.js @@ -18,7 +18,7 @@ define([ '../Renderer/ShaderSource', '../Renderer/VertexArray', '../Shaders/ShadowVolumeFS', - '../Shaders/ShadowVolumeVS', + '../Shaders/VectorTileVS', './BlendingState', './Cesium3DTileFeature', './ClassificationType', @@ -47,7 +47,7 @@ define([ ShaderSource, VertexArray, ShadowVolumeFS, - ShadowVolumeVS, + VectorTileVS, BlendingState, Cesium3DTileFeature, ClassificationType, @@ -297,11 +297,10 @@ define([ return; } - var vsSource = batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(ShadowVolumeVS); + var vsSource = batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(VectorTileVS); var fsSource = batchTable.getFragmentShaderCallback()(ShadowVolumeFS, false, undefined); var vs = new ShaderSource({ - defines : ['VECTOR_TILE'], sources : [vsSource] }); var fs = new ShaderSource({ @@ -317,8 +316,7 @@ define([ }); vs = new ShaderSource({ - defines : ['VECTOR_TILE'], - sources : [ShadowVolumeVS] + sources : [VectorTileVS] }); fs = new ShaderSource({ defines : ['VECTOR_TILE'], @@ -332,11 +330,10 @@ define([ attributeLocations : attributeLocations }); - vsSource = batchTable.getPickVertexShaderCallbackIgnoreShow('a_batchId')(ShadowVolumeVS); + vsSource = batchTable.getPickVertexShaderCallbackIgnoreShow('a_batchId')(VectorTileVS); fsSource = batchTable.getPickFragmentShaderCallbackIgnoreShow()(ShadowVolumeFS); var pickVS = new ShaderSource({ - defines : ['VECTOR_TILE'], sources : [vsSource] }); var pickFS = new ShaderSource({ diff --git a/Source/Shaders/Builtin/Functions/planeDistance.glsl b/Source/Shaders/Builtin/Functions/planeDistance.glsl new file mode 100644 index 00000000000..38db05a173c --- /dev/null +++ b/Source/Shaders/Builtin/Functions/planeDistance.glsl @@ -0,0 +1,13 @@ +/** + * Computes distance from an point to a plane, typically in eye space. + * + * @name czm_planeDistance + * @glslFunction + * + * param {vec4} plane A Plane in Hessian Normal Form. See Plane.js + * param {vec3} point A point in the same space as the plane. + * returns {float} The distance from the point to the plane. + */ +float czm_planeDistance(vec4 plane, vec3 point) { + return (dot(plane.xyz, point) + plane.w); +} diff --git a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl index 91bd5e3164f..c95f4f04739 100644 --- a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl +++ b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl @@ -53,3 +53,40 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate) return q; } + +/** + * Transforms a position given as window x/y and a depth or a log depth from window to eye coordinates. + * This function produces more accurate results for window positions with log depth than + * conventionally unpacking the log depth using czm_reverseLogDepth and using the standard version + * of czm_windowToEyeCoordinates. + * + * @name czm_windowToEyeCoordinates + * @glslFunction + * + * @param {vec2} fragmentCoordinateXY The XY position in window coordinates to transform. + * @param {float} depthOrLogDepth A depth or log depth for the fragment. + * + * @see czm_modelToWindowCoordinates + * @see czm_eyeToWindowCoordinates + * @see czm_inverseProjection + * @see czm_viewport + * @see czm_viewportTransformation + * + * @returns {vec4} The transformed position in eye coordinates. + */ +vec4 czm_windowToEyeCoordinates(vec2 fragmentCoordinateXY, float depthOrLogDepth) +{ + // See reverseLogDepth.glsl. This is separate to re-use the pow. +#ifdef LOG_DEPTH + float near = czm_currentFrustum.x; + float far = czm_currentFrustum.y; + float unscaledDepth = pow(2.0, depthOrLogDepth * czm_log2FarPlusOne) - 1.0; + vec4 windowCoord = vec4(fragmentCoordinateXY, far * (1.0 - near / unscaledDepth) / (far - near), 1.0); + vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord); + eyeCoordinate.w = 1.0 / unscaledDepth;\n // Better precision +#else + vec4 windowCoord = vec4(fragmentCoordinateXY, depthOrLogDepth, 1.0); + vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord); +#endif + return eyeCoordinate; +} diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl new file mode 100644 index 00000000000..3d285759fde --- /dev/null +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -0,0 +1,143 @@ +#ifdef GL_EXT_frag_depth +#extension GL_EXT_frag_depth : enable +#endif + +#ifdef TEXTURE_COORDINATES +#ifdef SPHERICAL +varying vec4 v_sphericalExtents; +#else // SPHERICAL +varying vec2 v_inversePlaneExtents; +varying vec4 v_westPlane; +varying vec4 v_southPlane; +#endif // SPHERICAL +varying vec4 v_stSineCosineUVScale; +#endif // TEXTURE_COORDINATES + +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#endif + +#ifdef NORMAL_EC +vec3 getEyeCoordinate3FromWindowCoordinate(vec2 fragCoord, float logDepthOrDepth) { + vec4 eyeCoordinate = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth); + return eyeCoordinate.xyz / eyeCoordinate.w; +} + +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)); + float downOrLeftLogDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, (glFragCoordXY - positiveOffset) / czm_viewport.zw)); + // Explicitly evaluate both paths + // Necessary for multifrustum and for edges of the screen + bvec2 upOrRightInBounds = lessThan(glFragCoordXY + positiveOffset, czm_viewport.zw); + float useUpOrRight = float(upOrRightLogDepth > 0.0 && upOrRightInBounds.x && upOrRightInBounds.y); + float useDownOrLeft = float(useUpOrRight == 0.0); + vec3 upOrRightEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY + positiveOffset, upOrRightLogDepth); + vec3 downOrLeftEC = getEyeCoordinate3FromWindowCoordinate(glFragCoordXY - positiveOffset, downOrLeftLogDepth); + return (upOrRightEC - (eyeCoordinate.xyz / eyeCoordinate.w)) * useUpOrRight + ((eyeCoordinate.xyz / eyeCoordinate.w) - downOrLeftEC) * useDownOrLeft; +} +#endif // NORMAL_EC + +void main(void) +{ +#ifdef REQUIRES_EC + float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, gl_FragCoord.xy / czm_viewport.zw)); + vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); +#endif + +#ifdef REQUIRES_WC + vec4 worldCoordinate4 = czm_inverseView * eyeCoordinate; + vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w; +#endif + +#ifdef TEXTURE_COORDINATES +#ifdef SPHERICAL + // Treat world coords as a sphere normal for spherical coordinates + vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate); + float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z; + float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w; +#else // SPHERICAL + // Unpack planes and transform to eye space + float u = czm_planeDistance(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.y; + float v = czm_planeDistance(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.x; +#endif // SPHERICAL +#endif // TEXTURE_COORDINATES + +#ifdef PICK +#ifdef CULL_FRAGMENTS + if (0.0 <= u && u <= 1.0 && 0.0 <= v && v <= 1.0) { + gl_FragColor.a = 1.0; // 0.0 alpha leads to discard from ShaderSource.createPickFragmentShaderSource + czm_writeDepthClampedToFarPlane(); + } +#else // CULL_FRAGMENTS + gl_FragColor.a = 1.0; +#endif // CULL_FRAGMENTS +#else // PICK + +#ifdef CULL_FRAGMENTS + if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { + discard; + } +#endif + +#ifdef NORMAL_EC + // Compute normal by sampling adjacent pixels in 2x2 block in screen space + vec3 downUp = vectorFromOffset(eyeCoordinate, vec2(0.0, 1.0)); + vec3 leftRight = vectorFromOffset(eyeCoordinate, vec2(1.0, 0.0)); + vec3 normalEC = normalize(cross(leftRight, downUp)); +#endif + + +#ifdef PER_INSTANCE_COLOR + +#ifdef FLAT + gl_FragColor = v_color; +#else // FLAT + czm_materialInput materialInput; + materialInput.normalEC = normalEC; + materialInput.positionToEyeEC = -eyeCoordinate.xyz; + czm_material material = czm_getDefaultMaterial(materialInput); + material.diffuse = v_color.rgb; + material.alpha = v_color.a; + + gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material); +#endif // FLAT + +#else // PER_INSTANCE_COLOR + +// Material support. +// USES_ is distinct from REQUIRES_, because some things are dependencies of each other or +// dependencies for culling but might not actually be used by the material. + + czm_materialInput materialInput; + +#ifdef USES_NORMAL_EC + materialInput.normalEC = normalEC; +#endif + +#ifdef USES_POSITION_TO_EYE_EC + materialInput.positionToEyeEC = -eyeCoordinate.xyz; +#endif + +#ifdef USES_TANGENT_TO_EYE + materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(worldCoordinate, normalEC); +#endif + +#ifdef USES_ST + materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5; + materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5; +#endif + + czm_material material = czm_getMaterial(materialInput); + +#ifdef FLAT + gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); +#else // FLAT + gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material); +#endif // FLAT + +#endif // PER_INSTANCE_COLOR + czm_writeDepthClampedToFarPlane(); +#endif // PICK +} diff --git a/Source/Shaders/ShadowVolumeAppearanceVS.glsl b/Source/Shaders/ShadowVolumeAppearanceVS.glsl new file mode 100644 index 00000000000..74f4dfca525 --- /dev/null +++ b/Source/Shaders/ShadowVolumeAppearanceVS.glsl @@ -0,0 +1,76 @@ +attribute vec3 position3DHigh; +attribute vec3 position3DLow; +attribute float batchId; + +#ifdef EXTRUDED_GEOMETRY +attribute vec3 extrudeDirection; + +uniform float u_globeMinimumAltitude; +#endif // EXTRUDED_GEOMETRY + +#ifdef PER_INSTANCE_COLOR +varying vec4 v_color; +#endif // PER_INSTANCE_COLOR + +#ifdef TEXTURE_COORDINATES +#ifdef SPHERICAL +varying vec4 v_sphericalExtents; +#else // SPHERICAL +varying vec2 v_inversePlaneExtents; +varying vec4 v_westPlane; +varying vec4 v_southPlane; +#endif // SPHERICAL +varying vec4 v_stSineCosineUVScale; +#endif // TEXTURE_COORDINATES + +void main() +{ + vec4 position = czm_computePosition(); + +#ifdef EXTRUDED_GEOMETRY + float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz)); + delta *= czm_sceneMode == czm_sceneMode3D ? 1.0 : 0.0; + + //extrudeDirection is zero for the top layer + position = position + vec4(extrudeDirection * delta, 0.0); +#endif + +#ifdef TEXTURE_COORDINATES +#ifdef SPHERICAL + v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); + v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); +#else // SPHERICAL +#ifdef COLUMBUS_VIEW_2D + vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId); + vec4 planes2D_low = czm_batchTable_planes2D_LOW(batchId); + vec3 southWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.xy), vec3(0.0, planes2D_low.xy))).xyz; + vec3 northWestCorner = (czm_modelViewRelativeToEye * czm_translateRelativeToEye(vec3(0.0, planes2D_high.x, planes2D_high.z), vec3(0.0, planes2D_low.x, planes2D_low.z))).xyz; + 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 + 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 // COLUMBUS_VIEW_2D + + vec3 eastWard = southEastCorner - southWestCorner; + float eastExtent = length(eastWard); + eastWard /= eastExtent; + + vec3 northWard = northWestCorner - southWestCorner; + float northExtent = length(northWard); + northWard /= northExtent; + + v_westPlane = vec4(eastWard, -dot(eastWard, southWestCorner)); + v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); + v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); +#endif // SPHERICAL + v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); +#endif // TEXTURE_COORDINATES + +#ifdef PER_INSTANCE_COLOR + v_color = czm_batchTable_color(batchId); +#endif + + gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position); +} diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl deleted file mode 100644 index 33554d4f3f5..00000000000 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ /dev/null @@ -1,34 +0,0 @@ -#ifdef VECTOR_TILE -attribute vec3 position; -attribute float a_batchId; - -uniform mat4 u_modifiedModelViewProjection; -#else -attribute vec3 position3DHigh; -attribute vec3 position3DLow; -attribute float batchId; -#endif - -#ifdef EXTRUDED_GEOMETRY -attribute vec3 extrudeDirection; - -uniform float u_globeMinimumAltitude; -#endif - -void main() -{ -#ifdef VECTOR_TILE - gl_Position = czm_depthClampFarPlane(u_modifiedModelViewProjection * vec4(position, 1.0)); -#else - vec4 position = czm_computePosition(); - -#ifdef EXTRUDED_GEOMETRY - float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz)); - delta *= czm_sceneMode == czm_sceneMode3D ? 1.0 : 0.0; - - //extrudeDirection is zero for the top layer - position = position + vec4(extrudeDirection * delta, 0.0); -#endif - gl_Position = czm_depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position); -#endif -} diff --git a/Source/Shaders/VectorTileVS.glsl b/Source/Shaders/VectorTileVS.glsl new file mode 100644 index 00000000000..6c7a5278e8a --- /dev/null +++ b/Source/Shaders/VectorTileVS.glsl @@ -0,0 +1,9 @@ +attribute vec3 position; +attribute float a_batchId; + +uniform mat4 u_modifiedModelViewProjection; + +void main() +{ + gl_Position = czm_depthClampFarPlane(u_modifiedModelViewProjection * vec4(position, 1.0)); +} diff --git a/Specs/Core/MathSpec.js b/Specs/Core/MathSpec.js index d9d07607981..60876d81eef 100644 --- a/Specs/Core/MathSpec.js +++ b/Specs/Core/MathSpec.js @@ -462,4 +462,10 @@ defineSuite([ expect(CesiumMath.fastApproximateAtan2(1.0, 1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR, CesiumMath.EPSILON3); expect(CesiumMath.fastApproximateAtan2(-1.0, 1.0)).toEqualEpsilon(CesiumMath.PI_OVER_FOUR + CesiumMath.PI_OVER_TWO, CesiumMath.EPSILON3); }); + + it('fastApproximateAtan2 throws if both arguments are zero', function() { + expect(function() { + CesiumMath.fastApproximateAtan2(0, 0); + }).toThrowDeveloperError(); + }); }); diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index 4138cbfd93b..911a327440a 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -124,6 +124,37 @@ defineSuite([ context : context, fragmentShader : fs }).contextToRender(); + + fs = + 'void main() { ' + + ' float z = czm_projection[3][2] / czm_projection[2][2];' + + ' float x = z / czm_projection[0][0];' + + ' float y = z / czm_projection[1][1];' + + ' vec3 pointEC = vec3(x, y, z);' + + ' vec4 actual = czm_windowToEyeCoordinates(vec2(0.0, 0.0), 0.0);' + + ' vec3 diff = actual.xyz - pointEC;' + + ' gl_FragColor = vec4(all(lessThan(diff, vec3(czm_epsilon6))));' + + '}'; + + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + + it('has czm_planeDistance', function() { + var fs = + 'void main() { ' + + ' vec4 plane = vec4(1.0, 0.0, 0.0, 0.0); ' + + ' vec3 point = vec3(1.0, 0.0, 0.0); ' + + ' float expected = 1.0; ' + + ' float actual = czm_planeDistance(plane, point); ' + + ' gl_FragColor = vec4(actual == expected); ' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); }); it('has czm_tangentToEyeSpaceMatrix', function() { diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index b83a9890fde..8291341509f 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -715,12 +715,6 @@ defineSuite([ var uniformState = scene.context.uniformState; - scene.copyGlobeDepth = false; - expect(scene).toRenderAndCall(function(rgba) { - expect(uniformState.globeDepthTexture).not.toBeDefined(); - }); - - scene.copyGlobeDepth = true; expect(scene).toRenderAndCall(function(rgba) { expect(uniformState.globeDepthTexture).toBeDefined(); }); diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index ade4d308560..b2f9d3be195 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -28,7 +28,7 @@ defineSuite([ PerInstanceColorAppearance) { 'use strict'; - // using ShadowVolumeVS directly fails on Travis with the --release test + // using ShadowVolumeAppearanceVS directly fails on Travis with the --release test var testVs = 'attribute vec3 position3DHigh;\n' + 'attribute vec3 position3DLow;\n' + @@ -207,91 +207,167 @@ defineSuite([ expect(ShadowVolumeAppearance.shouldUseSphericalCoordinates(largeTestRectangle)).toBe(true); }); - it('creates vertex shaders', function() { - // Check for varying declarations and that they get set + it('creates vertex shaders for color', function() { + // Check defines var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); - var sphericalTexturedVS3D = sphericalTexturedAppearance.createVertexShader(testVs, false); - expect(sphericalTexturedVS3D.includes('varying vec4 v_sphericalExtents;')).toBe(true); - expect(sphericalTexturedVS3D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); - - expect(sphericalTexturedVS3D.includes('v_sphericalExtents =')).toBe(true); - expect(sphericalTexturedVS3D.includes('v_stSineCosineUVScale =')).toBe(true); - - var sphericalTexturedVS2D = sphericalTexturedAppearance.createVertexShader(testVs, true); - expect(sphericalTexturedVS2D.includes('varying vec2 v_inversePlaneExtents;')).toBe(true); - expect(sphericalTexturedVS2D.includes('varying vec4 v_westPlane;')).toBe(true); - expect(sphericalTexturedVS2D.includes('varying vec4 v_southPlane;')).toBe(true); - expect(sphericalTexturedVS2D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); - - expect(sphericalTexturedVS2D.includes('v_inversePlaneExtents =')).toBe(true); - expect(sphericalTexturedVS2D.includes('v_westPlane =')).toBe(true); - expect(sphericalTexturedVS2D.includes('v_southPlane =')).toBe(true); - expect(sphericalTexturedVS2D.includes('v_stSineCosineUVScale =')).toBe(true); + var shaderSource = sphericalTexturedAppearance.createVertexShader([], testVs, false); + var defines = shaderSource.defines; + expect(defines.length).toEqual(2); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); + + // 2D variant + shaderSource = sphericalTexturedAppearance.createVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.length).toEqual(2); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('COLUMBUS_VIEW_2D')).not.toEqual(-1); + + // Unculled color appearance - no texcoords at all + var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance); + shaderSource = sphericalUnculledColorAppearance.createVertexShader([], testVs, false); + defines = shaderSource.defines; + expect(defines.length).toEqual(1); + expect(defines.indexOf('PER_INSTANCE_COLOR')).not.toEqual(-1); + + // 2D variant + shaderSource = sphericalUnculledColorAppearance.createVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.length).toEqual(1); + expect(defines.indexOf('PER_INSTANCE_COLOR')).not.toEqual(-1); + + // Planar textured, without culling + var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance); + shaderSource = planarTexturedAppearance.createVertexShader([], testVs, false); + defines = shaderSource.defines; + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.length).toEqual(1); + + shaderSource = planarTexturedAppearance.createVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('COLUMBUS_VIEW_2D')).not.toEqual(-1); + expect(defines.length).toEqual(2); + }); + it('creates vertex shaders for pick', function() { + // Check defines + var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); + var shaderSource = sphericalTexturedAppearance.createPickVertexShader([], testVs, false); + var defines = shaderSource.defines; + expect(defines.length).toEqual(2); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); + + // 2D variant + shaderSource = sphericalTexturedAppearance.createPickVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.length).toEqual(2); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('COLUMBUS_VIEW_2D')).not.toEqual(-1); + + // Unculled color appearance - no texcoords at all var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(false, false, perInstanceColorMaterialAppearance); - var sphericalUnculledColorVS3D = sphericalUnculledColorAppearance.createVertexShader(testVs, false); - expect(sphericalUnculledColorVS3D.includes('varying vec4 v_color;')).toBe(true); - expect(sphericalUnculledColorVS3D.includes('v_color =')).toBe(true); + shaderSource = sphericalUnculledColorAppearance.createPickVertexShader([], testVs, false); + defines = shaderSource.defines; + expect(defines.length).toEqual(0); - var sphericalUnculledColorVS2D = sphericalUnculledColorAppearance.createVertexShader(testVs, true); - expect(sphericalUnculledColorVS2D.includes('varying vec4 v_color;')).toBe(true); - expect(sphericalUnculledColorVS2D.includes('v_color =')).toBe(true); + // 2D variant + shaderSource = sphericalUnculledColorAppearance.createPickVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.length).toEqual(0); + // Planar textured, without culling var planarTexturedAppearance = new ShadowVolumeAppearance(false, true, textureMaterialAppearance); - var planarTexturedAppearanceVS3D = planarTexturedAppearance.createVertexShader(testVs, false); + shaderSource = planarTexturedAppearance.createPickVertexShader([], testVs, false); + defines = shaderSource.defines; + expect(defines.length).toEqual(0); - expect(planarTexturedAppearanceVS3D.includes('varying vec2 v_inversePlaneExtents;')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_westPlane;')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_southPlane;')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('varying vec4 v_stSineCosineUVScale;')).toBe(true); - - expect(planarTexturedAppearanceVS3D.includes('v_inversePlaneExtents =')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('v_westPlane =')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('v_southPlane =')).toBe(true); - expect(planarTexturedAppearanceVS3D.includes('v_stSineCosineUVScale =')).toBe(true); + shaderSource = planarTexturedAppearance.createPickVertexShader([], testVs, true); + defines = shaderSource.defines; + expect(defines.length).toEqual(0); }); - it('creates fragment shaders', function() { + it('creates fragment shaders for color and pick', function() { + // Check defines var sphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, textureMaterialAppearance); - var sphericalTexturedFS3D = sphericalTexturedAppearance.createAppearanceFragmentShader(false); + var shaderSource = sphericalTexturedAppearance.createFragmentShader(false); + var defines = shaderSource.defines; // Check material hookups, discard for culling, and phong shading - expect(sphericalTexturedFS3D.includes('.normalEC =')).toBe(true); - expect(sphericalTexturedFS3D.includes('.positionToEyeEC =')).toBe(true); - expect(sphericalTexturedFS3D.includes('.tangentToEyeMatrix =')).toBe(true); - expect(sphericalTexturedFS3D.includes('.st.x =')).toBe(true); - expect(sphericalTexturedFS3D.includes('.st.y =')).toBe(true); - expect(sphericalTexturedFS3D.includes('discard;')).toBe(true); - expect(sphericalTexturedFS3D.includes('czm_phong')).toBe(true); - - var sphericalTexturedFS2D = sphericalTexturedAppearance.createAppearanceFragmentShader(true); - - expect(sphericalTexturedFS2D.includes('.normalEC =')).toBe(true); - expect(sphericalTexturedFS2D.includes('.positionToEyeEC =')).toBe(true); - expect(sphericalTexturedFS2D.includes('.tangentToEyeMatrix =')).toBe(true); - expect(sphericalTexturedFS2D.includes('.st.x =')).toBe(true); - expect(sphericalTexturedFS2D.includes('.st.y =')).toBe(true); - expect(sphericalTexturedFS2D.includes('discard;')).toBe(true); - expect(sphericalTexturedFS2D.includes('czm_phong')).toBe(true); - - var planarColorAppearance = new ShadowVolumeAppearance(true, false, perInstanceColorMaterialAppearance); - var planarColorFS3D = planarColorAppearance.createAppearanceFragmentShader(false); - expect(planarColorFS3D.includes('= v_color')).toBe(true); - expect(planarColorFS3D.includes('varying vec4 v_color;')).toBe(true); - expect(planarColorFS3D.includes('discard;')).toBe(true); - expect(planarColorFS3D.includes('czm_phong')).toBe(true); + expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_EC')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_WC')).not.toEqual(-1); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('CULL_FRAGMENTS')).not.toEqual(-1); + expect(defines.indexOf('NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_POSITION_TO_EYE_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_TANGENT_TO_EYE')).not.toEqual(-1); + expect(defines.indexOf('USES_ST')).not.toEqual(-1); + expect(defines.length).toEqual(10); + + // 2D case + shaderSource = sphericalTexturedAppearance.createFragmentShader(true); + defines = shaderSource.defines; + + expect(defines.indexOf('REQUIRES_EC')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_WC')).not.toEqual(-1); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('CULL_FRAGMENTS')).not.toEqual(-1); + expect(defines.indexOf('NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_POSITION_TO_EYE_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_TANGENT_TO_EYE')).not.toEqual(-1); + expect(defines.indexOf('USES_ST')).not.toEqual(-1); + expect(defines.length).toEqual(9); + + // Culling with planar texture coordinates on a per-color material + var planarColorAppearance = new ShadowVolumeAppearance(true, true, perInstanceColorMaterialAppearance); + shaderSource = planarColorAppearance.createFragmentShader(false); + defines = shaderSource.defines; + + expect(defines.indexOf('PER_INSTANCE_COLOR')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_EC')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_WC')).not.toEqual(-1); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('CULL_FRAGMENTS')).not.toEqual(-1); + expect(defines.indexOf('NORMAL_EC')).not.toEqual(-1); + expect(defines.length).toEqual(6); // Pick - var pickColorFS2D = planarColorAppearance.createPickingFragmentShader(true); - expect(pickColorFS2D.includes('gl_FragColor.a = 1.0')).toBe(true); + shaderSource = planarColorAppearance.createPickFragmentShader(true); + defines = shaderSource.defines; + + expect(defines.indexOf('REQUIRES_EC')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_WC')).not.toEqual(-1); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('CULL_FRAGMENTS')).not.toEqual(-1); + expect(defines.indexOf('PICK')).not.toEqual(-1); + expect(defines.length).toEqual(5); // Flat var flatSphericalTexturedAppearance = new ShadowVolumeAppearance(true, false, flatTextureMaterialAppearance); - var flatSphericalTexturedFS3D = flatSphericalTexturedAppearance.createAppearanceFragmentShader(false); - expect(flatSphericalTexturedFS3D.includes('gl_FragColor = vec4')).toBe(true); + shaderSource = flatSphericalTexturedAppearance.createFragmentShader(false); + defines = shaderSource.defines; + expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_EC')).not.toEqual(-1); + expect(defines.indexOf('REQUIRES_WC')).not.toEqual(-1); + expect(defines.indexOf('TEXTURE_COORDINATES')).not.toEqual(-1); + expect(defines.indexOf('CULL_FRAGMENTS')).not.toEqual(-1); + expect(defines.indexOf('NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_NORMAL_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_POSITION_TO_EYE_EC')).not.toEqual(-1); + expect(defines.indexOf('USES_ST')).not.toEqual(-1); + expect(defines.indexOf('FLAT')).not.toEqual(-1); + expect(defines.length).toEqual(10); var flatSphericalColorAppearance = new ShadowVolumeAppearance(false, false, flatPerInstanceColorMaterialAppearance); - var flatSphericalColorFS3D = flatSphericalColorAppearance.createAppearanceFragmentShader(false); - expect(flatSphericalColorFS3D.includes('gl_FragColor = v_color;')).toBe(true); + shaderSource = flatSphericalColorAppearance.createFragmentShader(false); + defines = shaderSource.defines; + expect(defines.indexOf('SPHERICAL')).not.toEqual(-1); + expect(defines.indexOf('PER_INSTANCE_COLOR')).not.toEqual(-1); + expect(defines.indexOf('FLAT')).not.toEqual(-1); + expect(defines.length).toEqual(3); }); }); From 5b0c259d1bb5f5c0cc29fb52aa05d151a323cabf Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 3 May 2018 16:42:56 -0400 Subject: [PATCH 27/40] add fallback for IE --- Source/DataSources/Entity.js | 14 + Source/DataSources/GeometryUpdater.js | 7 +- Source/DataSources/GeometryVisualizer.js | 31 +- .../StaticGroundGeometryColorBatch.js | 356 ++++++++++++++++++ Source/Scene/GroundPrimitive.js | 53 ++- Specs/DataSources/GeometryVisualizerSpec.js | 67 ++++ .../StaticGroundGeometryColorBatchSpec.js | 239 ++++++++++++ Specs/Scene/GroundPrimitiveSpec.js | 4 +- 8 files changed, 755 insertions(+), 16 deletions(-) create mode 100644 Source/DataSources/StaticGroundGeometryColorBatch.js create mode 100644 Specs/DataSources/StaticGroundGeometryColorBatchSpec.js diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index 8588ecbec11..b9d400c6693 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -11,6 +11,7 @@ define([ '../Core/Matrix4', '../Core/Quaternion', '../Core/Transforms', + '../Scene/GroundPrimitive', './BillboardGraphics', './BoxGraphics', './ConstantPositionProperty', @@ -45,6 +46,7 @@ define([ Matrix4, Quaternion, Transforms, + GroundPrimitive, BillboardGraphics, BoxGraphics, ConstantPositionProperty, @@ -617,5 +619,17 @@ define([ return result; }; + /** + * Checks if the given Scene supports materials besides Color on Entities draped on terrain. + * If this feature is not supported, Entities with non-color materials but no `height` will + * instead be rendered as if height is 0. + * + * @param {Scene} scene The current scene. + * @returns {Boolean} Whether or not the current scene supports materials for entities on terrain. + */ + Entity.supportsMaterialsforEntitiesOnTerrain = function(scene) { + return GroundPrimitive.supportsMaterials(scene); + }; + return Entity; }); diff --git a/Source/DataSources/GeometryUpdater.js b/Source/DataSources/GeometryUpdater.js index 0846738c42f..d6c7c96a159 100644 --- a/Source/DataSources/GeometryUpdater.js +++ b/Source/DataSources/GeometryUpdater.js @@ -14,6 +14,7 @@ define([ '../Scene/ShadowMode', './ColorMaterialProperty', './ConstantProperty', + './Entity', './Property' ], function( Check, @@ -31,6 +32,7 @@ define([ ShadowMode, ColorMaterialProperty, ConstantProperty, + Entity, Property) { 'use strict'; @@ -88,6 +90,7 @@ define([ this._geometryPropertyName = geometryPropertyName; this._id = geometryPropertyName + '-' + entity.id; this._observedPropertyNames = options.observedPropertyNames; + this._supportsMaterialsforEntitiesOnTerrain = Entity.supportsMaterialsforEntitiesOnTerrain(options.scene); this._onEntityPropertyChanged(entity, geometryPropertyName, entity[geometryPropertyName], undefined); } @@ -460,7 +463,9 @@ define([ this._fillEnabled = fillEnabled; - var onTerrain = this._isOnTerrain(entity, geometry); + var onTerrain = this._isOnTerrain(entity, geometry) && + (this._supportsMaterialsforEntitiesOnTerrain || this._materialProperty instanceof ColorMaterialProperty); + if (outlineEnabled && onTerrain) { oneTimeWarning(oneTimeWarning.geometryOutlines); outlineEnabled = false; diff --git a/Source/DataSources/GeometryVisualizer.js b/Source/DataSources/GeometryVisualizer.js index be777ad5bee..ca363c2404a 100644 --- a/Source/DataSources/GeometryVisualizer.js +++ b/Source/DataSources/GeometryVisualizer.js @@ -19,12 +19,14 @@ define([ './DynamicGeometryBatch', './EllipseGeometryUpdater', './EllipsoidGeometryUpdater', + './Entity', './PlaneGeometryUpdater', './PolygonGeometryUpdater', './PolylineVolumeGeometryUpdater', './RectangleGeometryUpdater', './StaticGeometryColorBatch', './StaticGeometryPerMaterialBatch', + './StaticGroundGeometryColorBatch', './StaticGroundGeometryPerMaterialBatch', './StaticOutlineGeometryBatch', './WallGeometryUpdater' @@ -49,12 +51,14 @@ define([ DynamicGeometryBatch, EllipseGeometryUpdater, EllipsoidGeometryUpdater, + Entity, PlaneGeometryUpdater, PolygonGeometryUpdater, PolylineVolumeGeometryUpdater, RectangleGeometryUpdater, StaticGeometryColorBatch, StaticGeometryPerMaterialBatch, + StaticGroundGeometryColorBatch, StaticGroundGeometryPerMaterialBatch, StaticOutlineGeometryBatch, WallGeometryUpdater) { @@ -144,6 +148,9 @@ define([ this._openColorBatches = new Array(numberOfShadowModes); this._openMaterialBatches = new Array(numberOfShadowModes); + var supportsMaterialsforEntitiesOnTerrain = Entity.supportsMaterialsforEntitiesOnTerrain(scene); + this._supportsMaterialsforEntitiesOnTerrain = supportsMaterialsforEntitiesOnTerrain; + var i; for (i = 0; i < numberOfShadowModes; ++i) { this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i); @@ -155,14 +162,25 @@ define([ } var numberOfClassificationTypes = ClassificationType.NUMBER_OF_CLASSIFICATION_TYPES; - this._groundColorBatches = new Array(numberOfClassificationTypes); - this._groundMaterialBatches = new Array(numberOfClassificationTypes); - - for (i = 0; i < numberOfClassificationTypes; ++i) { - this._groundColorBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance, i); - this._groundMaterialBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance, i); + var groundMaterialBatches; + var groundColorBatches = new Array(numberOfClassificationTypes); + if (supportsMaterialsforEntitiesOnTerrain) { + groundMaterialBatches = new Array(numberOfClassificationTypes); + + for (i = 0; i < numberOfClassificationTypes; ++i) { + groundColorBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance, i); + groundMaterialBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance, i); + } + } else { + groundMaterialBatches = []; + for (i = 0; i < numberOfClassificationTypes; ++i) { + groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, i); + } } + this._groundMaterialBatches = groundMaterialBatches; + this._groundColorBatches = groundColorBatches; + this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatches, this._groundMaterialBatches, this._dynamicBatch); @@ -380,6 +398,7 @@ define([ if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { this._groundColorBatches[classificationType].add(time, updater); } else { + // If unsupported, updater will not be on terrain. this._groundMaterialBatches[classificationType].add(time, updater); } } else if (updater.isClosed) { diff --git a/Source/DataSources/StaticGroundGeometryColorBatch.js b/Source/DataSources/StaticGroundGeometryColorBatch.js new file mode 100644 index 00000000000..9dcfc20bfde --- /dev/null +++ b/Source/DataSources/StaticGroundGeometryColorBatch.js @@ -0,0 +1,356 @@ +define([ + '../Core/AssociativeArray', + '../Core/Color', + '../Core/defined', + '../Core/DistanceDisplayCondition', + '../Core/DistanceDisplayConditionGeometryInstanceAttribute', + '../Core/ShowGeometryInstanceAttribute', + '../Scene/GroundPrimitive', + './BoundingSphereState', + './Property' + ], function( + AssociativeArray, + Color, + defined, + DistanceDisplayCondition, + DistanceDisplayConditionGeometryInstanceAttribute, + ShowGeometryInstanceAttribute, + GroundPrimitive, + BoundingSphereState, + Property) { + 'use strict'; + + var colorScratch = new Color(); + var distanceDisplayConditionScratch = new DistanceDisplayCondition(); + var defaultDistanceDisplayCondition = new DistanceDisplayCondition(); + + function Batch(primitives, classificationType, color, key) { + this.primitives = primitives; + this.classificationType = classificationType; + this.color = color; + this.key = key; + this.createPrimitive = false; + this.waitingOnCreate = false; + this.primitive = undefined; + this.oldPrimitive = undefined; + this.geometry = new AssociativeArray(); + this.updaters = new AssociativeArray(); + this.updatersWithAttributes = new AssociativeArray(); + this.attributes = new AssociativeArray(); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); + this.itemsToRemove = []; + this.isDirty = false; + } + + Batch.prototype.add = function(updater, instance) { + var id = updater.id; + this.createPrimitive = true; + this.geometry.set(id, instance); + this.updaters.set(id, updater); + if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) { + this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(updater.id, updater); + } + })); + } + }; + + Batch.prototype.remove = function(updater) { + var id = updater.id; + this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + } + }; + + var scratchArray = new Array(4); + + Batch.prototype.update = function(time) { + var isUpdated = true; + var removedCount = 0; + var primitive = this.primitive; + var primitives = this.primitives; + var attributes; + var i; + + if (this.createPrimitive) { + var geometries = this.geometry.values; + var geometriesLength = geometries.length; + if (geometriesLength > 0) { + if (defined(primitive)) { + if (!defined(this.oldPrimitive)) { + this.oldPrimitive = primitive; + } else { + primitives.remove(primitive); + } + } + + for (i = 0; i < geometriesLength; i++) { + var geometryItem = geometries[i]; + var originalAttributes = geometryItem.attributes; + attributes = this.attributes.get(geometryItem.id.id); + + if (defined(attributes)) { + if (defined(originalAttributes.show)) { + originalAttributes.show.value = attributes.show; + } + if (defined(originalAttributes.color)) { + originalAttributes.color.value = attributes.color; + } + } + } + + primitive = new GroundPrimitive({ + show : false, + asynchronous : true, + geometryInstances : geometries, + classificationType : this.classificationType + }); + primitives.add(primitive); + isUpdated = false; + } else { + if (defined(primitive)) { + primitives.remove(primitive); + primitive = undefined; + } + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } + } + + this.attributes.removeAll(); + this.primitive = primitive; + this.createPrimitive = false; + this.waitingOnCreate = true; + } else if (defined(primitive) && primitive.ready) { + primitive.show = true; + if (defined(this.oldPrimitive)) { + primitives.remove(this.oldPrimitive); + this.oldPrimitive = undefined; + } + var updatersWithAttributes = this.updatersWithAttributes.values; + var length = updatersWithAttributes.length; + var waitingOnCreate = this.waitingOnCreate; + for (i = 0; i < length; i++) { + var updater = updatersWithAttributes[i]; + var instance = this.geometry.get(updater.id); + + attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) { + var colorProperty = updater.fillMaterialProperty.color; + var fillColor = Property.getValueOrDefault(colorProperty, time, Color.WHITE, colorScratch); + + if (!Color.equals(attributes._lastColor, fillColor)) { + attributes._lastColor = Color.clone(fillColor, attributes._lastColor); + var color = this.color; + var newColor = fillColor.toBytes(scratchArray); + if (color[0] !== newColor[0] || color[1] !== newColor[1] || + color[2] !== newColor[2] || color[3] !== newColor[3]) { + this.itemsToRemove[removedCount++] = updater; + } + } + } + + var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time)); + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + + var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty; + if (!Property.isConstant(distanceDisplayConditionProperty)) { + var distanceDisplayCondition = Property.getValueOrDefault(distanceDisplayConditionProperty, time, defaultDistanceDisplayCondition, distanceDisplayConditionScratch); + if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) { + attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition); + attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition); + } + } + } + + this.updateShows(primitive); + this.waitingOnCreate = false; + } else if (defined(primitive) && !primitive.ready) { + isUpdated = false; + } + this.itemsToRemove.length = removedCount; + return isUpdated; + }; + + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var instance = this.geometry.get(updater.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = updater.entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + + Batch.prototype.contains = function(updater) { + return this.updaters.contains(updater.id); + }; + + Batch.prototype.getBoundingSphere = function(updater, result) { + var primitive = this.primitive; + if (!primitive.ready) { + return BoundingSphereState.PENDING; + } + + var bs = primitive.getBoundingSphere(updater.entity); + if (!defined(bs)) { + return BoundingSphereState.FAILED; + } + + bs.clone(result); + return BoundingSphereState.DONE; + }; + + Batch.prototype.removeAllPrimitives = function() { + var primitives = this.primitives; + + var primitive = this.primitive; + if (defined(primitive)) { + primitives.remove(primitive); + this.primitive = undefined; + this.geometry.removeAll(); + this.updaters.removeAll(); + } + + var oldPrimitive = this.oldPrimitive; + if (defined(oldPrimitive)) { + primitives.remove(oldPrimitive); + this.oldPrimitive = undefined; + } + }; + + /** + * @private + */ + function StaticGroundGeometryColorBatch(primitives, classificationType) { + this._batches = new AssociativeArray(); + this._primitives = primitives; + this._classificationType = classificationType; + } + + StaticGroundGeometryColorBatch.prototype.add = function(time, updater) { + var instance = updater.createFillGeometryInstance(time); + var batches = this._batches; + // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key + var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0]; + var batch; + if (batches.contains(batchKey)) { + batch = batches.get(batchKey); + } else { + batch = new Batch(this._primitives, this._classificationType, instance.attributes.color.value, batchKey); + batches.set(batchKey, batch); + } + batch.add(updater, instance); + return batch; + }; + + StaticGroundGeometryColorBatch.prototype.remove = function(updater) { + var batchesArray = this._batches.values; + var count = batchesArray.length; + for (var i = 0; i < count; ++i) { + if (batchesArray[i].remove(updater)) { + return; + } + } + }; + + StaticGroundGeometryColorBatch.prototype.update = function(time) { + var i; + var updater; + + //Perform initial update + var isUpdated = true; + var batches = this._batches; + var batchesArray = batches.values; + var batchCount = batchesArray.length; + for (i = 0; i < batchCount; ++i) { + isUpdated = batchesArray[i].update(time) && isUpdated; + } + + //If any items swapped between batches we need to move them + for (i = 0; i < batchCount; ++i) { + var oldBatch = batchesArray[i]; + var itemsToRemove = oldBatch.itemsToRemove; + var itemsToMoveLength = itemsToRemove.length; + for (var j = 0; j < itemsToMoveLength; j++) { + updater = itemsToRemove[j]; + oldBatch.remove(updater); + var newBatch = this.add(time, updater); + oldBatch.isDirty = true; + newBatch.isDirty = true; + } + } + + //If we moved anything around, we need to re-build the primitive and remove empty batches + var batchesArrayCopy = batchesArray.slice(); + var batchesCopyCount = batchesArrayCopy.length; + for (i = 0; i < batchesCopyCount; ++i) { + var batch = batchesArrayCopy[i]; + if (batch.isDirty) { + isUpdated = batchesArrayCopy[i].update(time) && isUpdated; + batch.isDirty = false; + } + if (batch.geometry.length === 0) { + batches.remove(batch.key); + } + } + + return isUpdated; + }; + + StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(updater, result) { + var batchesArray = this._batches.values; + var batchCount = batchesArray.length; + for (var i = 0; i < batchCount; ++i) { + var batch = batchesArray[i]; + if (batch.contains(updater)) { + return batch.getBoundingSphere(updater, result); + } + } + + return BoundingSphereState.FAILED; + }; + + StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() { + var batchesArray = this._batches.values; + var batchCount = batchesArray.length; + for (var i = 0; i < batchCount; ++i) { + batchesArray[i].removeAllPrimitives(); + } + }; + + return StaticGroundGeometryColorBatch; +}); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index a4b83f96d3c..8b04f8f91d6 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -4,6 +4,8 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', + '../Core/Check', + '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -30,6 +32,8 @@ define([ Cartesian2, Cartesian3, Cartographic, + Check, + ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, @@ -66,6 +70,9 @@ define([ * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. * + * Support for the WEBGL_depth_texture extension is required to use GeometryInstances with different PerInstanceColors + * or materials besides PerInstanceColorAppearance. + * * Textured GroundPrimitives were designed for notional patterns and are not meant for precisely mapping * textures to terrain - for that use case, use {@link SingleTileImageryProvider}. * @@ -83,7 +90,7 @@ define([ * * @param {Object} [options] Object with the following properties: * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render. - * @param {Appearance} [options.appearance] The appearance used to render the primitive. Defaults to PerInstanceColorAppearance when GeometryInstances have a color attribute. + * @param {Appearance} [options.appearance] The appearance used to render the primitive. Defaults to a flat PerInstanceColorAppearance when GeometryInstances have a color attribute. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. @@ -786,7 +793,18 @@ define([ this._minHeight = this._minTerrainHeight * exaggeration; this._maxHeight = this._maxTerrainHeight * exaggeration; - // Determine whether to add spherical or planar extent attributes + // Determine whether to add spherical or planar extent attributes for computing texture coordinates. + // This depends on the size of the GeometryInstances. + + // If all the GeometryInstances have the same per-instance color, + // don't bother with texture coordinates at all. + var allSameColor = false; + var color; + var attributes; + if (length > 0 && defined(instances[0].attributes) && defined(instances[0].attributes.color)) { + color = instances[0].attributes.color; + allSameColor = true; + } var usePlanarExtents = true; for (i = 0; i < length; ++i) { instance = instances[i]; @@ -794,7 +812,10 @@ define([ rectangle = getRectangle(frameState, geometry); if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) { usePlanarExtents = false; - break; + } + attributes = instance.attributes; + if (defined(color) && defined(attributes) && defined(attributes.color)) { + allSameColor = allSameColor && ColorGeometryInstanceAttribute.equals(color, attributes.color); } } @@ -804,12 +825,15 @@ define([ instanceType = geometry.constructor; rectangle = getRectangle(frameState, geometry); - var attributes; - if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, this._maxHeight, geometry._stRotation); + if (!allSameColor) { + if (usePlanarExtents) { + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, this._maxHeight, geometry._stRotation); + } else { + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); + } } else { - attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); + attributes = {}; } var instanceAttributes = instance.attributes; @@ -931,5 +955,20 @@ define([ return destroyObject(this); }; + /** + * Checks if the given Scene supports materials on GroundPrimitives. + * Materials on GroundPrimitives require support for the WEBGL_depth_texture extension. + * + * @param {Scene} scene The current scene. + * @returns {Boolean} Whether or not the current scene supports materials on GroundPrimitives. + */ + GroundPrimitive.supportsMaterials = function(scene) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('scene', scene); + //>>includeEnd('debug'); + + return scene.frameState.context.depthTexture; + }; + return GroundPrimitive; }); diff --git a/Specs/DataSources/GeometryVisualizerSpec.js b/Specs/DataSources/GeometryVisualizerSpec.js index f2c35ededa9..0f51eccf215 100644 --- a/Specs/DataSources/GeometryVisualizerSpec.js +++ b/Specs/DataSources/GeometryVisualizerSpec.js @@ -840,4 +840,71 @@ defineSuite([ visualizer.destroy(); }); }); + + it('batches ground entities by identical color if ground entity materials are not supported', function() { + spyOn(GroundPrimitive, 'supportsMaterials').and.callFake(function() { + return false; + }); + var entities = new EntityCollection(); + var visualizer = new GeometryVisualizer(scene, entities, scene.primitives, scene.groundPrimitives); + + var blueColor = Color.BLUE.withAlpha(0.5); + entities.add({ + position : new Cartesian3(1, 2, 3), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : new Cartesian3(12, 34, 45), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : new Cartesian3(123, 456, 789), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : Color.BLUE.withAlpha(0.6) + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(2); + + entities.removeAll(); + visualizer.destroy(); + }); + }); + }, 'WebGL'); diff --git a/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js b/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js new file mode 100644 index 00000000000..32d95f9f05f --- /dev/null +++ b/Specs/DataSources/StaticGroundGeometryColorBatchSpec.js @@ -0,0 +1,239 @@ +defineSuite([ + 'DataSources/StaticGroundGeometryColorBatch', + 'Core/Cartesian3', + 'Core/Color', + 'Core/DistanceDisplayCondition', + 'Core/JulianDate', + 'Core/Math', + 'Core/TimeInterval', + 'Core/TimeIntervalCollection', + 'DataSources/CallbackProperty', + 'DataSources/ColorMaterialProperty', + 'DataSources/EllipseGeometryUpdater', + 'DataSources/Entity', + 'DataSources/TimeIntervalCollectionProperty', + 'Scene/ClassificationType', + 'Scene/GroundPrimitive', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + StaticGroundGeometryColorBatch, + Cartesian3, + Color, + DistanceDisplayCondition, + JulianDate, + CesiumMath, + TimeInterval, + TimeIntervalCollection, + CallbackProperty, + ColorMaterialProperty, + EllipseGeometryUpdater, + Entity, + TimeIntervalCollectionProperty, + ClassificationType, + GroundPrimitive, + createScene, + pollToPromise) { + 'use strict'; + + var time = JulianDate.now(); + var scene; + beforeAll(function() { + scene = createScene(); + + return GroundPrimitive.initializeTerrainHeights(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + + // Leave ground primitive uninitialized + GroundPrimitive._initialized = false; + GroundPrimitive._initPromise = undefined; + GroundPrimitive._terrainHeights = undefined; + }); + + function computeKey(color) { + var ui8 = new Uint8Array(color); + var ui32 = new Uint32Array(ui8.buffer); + return ui32[0]; + } + + it('updates color attribute after rebuilding primitive', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); + var entity = new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + show : new CallbackProperty(function() { + return true; + }, false), + material : Color.RED + } + }); + + var updater = new EllipseGeometryUpdater(entity, scene); + batch.add(time, updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + var red = [255, 0, 0, 255]; + var redKey = computeKey(red); + expect(attributes.color).toEqual(red); + + // Verify we have 1 batch with the key for red + expect(batch._batches.length).toEqual(1); + expect(batch._batches.contains(redKey)).toBe(true); + expect(batch._batches.get(redKey).key).toEqual(redKey); + + entity.ellipse.material = Color.GREEN; + updater._onEntityPropertyChanged(entity, 'ellipse'); + batch.remove(updater); + batch.add(time, updater); + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + var green = [0, 128, 0, 255]; + var greenKey = computeKey(green); + expect(attributes.color).toEqual(green); + + // Verify we have 1 batch with the key for green + expect(batch._batches.length).toEqual(1); + expect(batch._batches.contains(greenKey)).toBe(true); + expect(batch._batches.get(greenKey).key).toEqual(greenKey); + + batch.removeAllPrimitives(); + }); + }); + }); + + it('updates with sampled distance display condition out of range', function() { + var validTime = JulianDate.fromIso8601('2018-02-14T04:10:00+1100'); + var ddc = new TimeIntervalCollectionProperty(); + ddc.intervals.addInterval(TimeInterval.fromIso8601({ + iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:15:00+1100', + data: new DistanceDisplayCondition(1.0, 2.0) + })); + var entity = new Entity({ + availability: new TimeIntervalCollection([TimeInterval.fromIso8601({iso8601: '2018-02-14T04:00:00+1100/2018-02-14T04:30:00+1100'})]), + position : new Cartesian3(1234, 5678, 9101112), + ellipse: { + semiMajorAxis : 2, + semiMinorAxis : 1, + material: Color.RED, + distanceDisplayCondition: ddc + } + }); + + var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); + + var updater = new EllipseGeometryUpdater(entity, scene); + batch.add(validTime, updater); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = batch.update(validTime); + scene.render(validTime); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + var attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqualEpsilon([1.0, 2.0], CesiumMath.EPSILON6); + + batch.update(time); + scene.render(time); + + primitive = scene.groundPrimitives.get(0); + attributes = primitive.getGeometryInstanceAttributes(entity); + expect(attributes.distanceDisplayCondition).toEqual([0.0, Infinity]); + + batch.removeAllPrimitives(); + }); + }); + + it('shows only one primitive while rebuilding primitive', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + var batch = new StaticGroundGeometryColorBatch(scene.groundPrimitives, ClassificationType.BOTH); + function buildEntity() { + return new Entity({ + position : new Cartesian3(1234, 5678, 9101112), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + height : 0, + outline : true, + outlineColor : Color.RED.withAlpha(0.5) + } + }); + } + + function renderScene() { + scene.initializeFrame(); + var isUpdated = batch.update(time); + scene.render(time); + return isUpdated; + } + + var entity1 = buildEntity(); + var entity2 = buildEntity(); + + var updater1 = new EllipseGeometryUpdater(entity1, scene); + var updater2 = new EllipseGeometryUpdater(entity2, scene); + + batch.add(time, updater1); + return pollToPromise(renderScene) + .then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + expect(primitive.show).toBeTruthy(); + }) + .then(function() { + batch.add(time, updater2); + }) + .then(function() { + return pollToPromise(function() { + renderScene(); + return scene.groundPrimitives.length === 2; + }); + }) + .then(function() { + var showCount = 0; + expect(scene.groundPrimitives.length).toEqual(2); + showCount += !!scene.groundPrimitives.get(0).show; + showCount += !!scene.groundPrimitives.get(1).show; + expect(showCount).toEqual(1); + }) + .then(function() { + return pollToPromise(renderScene); + }) + .then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + var primitive = scene.groundPrimitives.get(0); + expect(primitive.show).toBeTruthy(); + + batch.removeAllPrimitives(); + }); + }); +}); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index 04e240ef418..ea0a1b404cc 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -464,7 +464,7 @@ defineSuite([ }); it('renders small GeometryInstances with texture', function() { - if (!GroundPrimitive.isSupported(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { return; } @@ -495,7 +495,7 @@ defineSuite([ }); it('renders large GeometryInstances with texture', function() { - if (!GroundPrimitive.isSupported(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { return; } From 606188015f753f67efbf3a3305c1cc08d34d11c4 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 3 May 2018 17:45:06 -0400 Subject: [PATCH 28/40] added development Sandcastles, fixed dynamic Entities, updated CHANGES.md --- .../Ground Primitive Materials.html | 487 ++++++++++++++++++ .../Ground Primitive Materials.jpg | Bin 0 -> 20939 bytes .../development/Terrain Entity Batching.html | 125 +++++ .../development/Terrain Entity Batching.jpg | Bin 0 -> 22837 bytes CHANGES.md | 7 + Source/DataSources/DynamicGeometryUpdater.js | 3 +- 6 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 Apps/Sandcastle/gallery/development/Ground Primitive Materials.html create mode 100644 Apps/Sandcastle/gallery/development/Ground Primitive Materials.jpg create mode 100644 Apps/Sandcastle/gallery/development/Terrain Entity Batching.html create mode 100644 Apps/Sandcastle/gallery/development/Terrain Entity Batching.jpg diff --git a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html new file mode 100644 index 00000000000..c23c3fcbdeb --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html @@ -0,0 +1,487 @@ + + + + + + + + + Cesium Demo + + + + + + +

+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.jpg b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a3cf86f4672ac63fbdae20869402e2fdf1c3744 GIT binary patch literal 20939 zcmeHuWmH_twr)4>?(Qy)ySux)ySoH}ySoH;cMtCF1OfyP5C|HAB|MTX=iGbGc;mj` zZ&&x~uBs_()>pM=&ys%m@UjCyk(HE@1OS0Rfb{DN@Un|4E#Ylr2>>W40O$Y!01N;c zhzEdt%>iF8Qy~7|d0QY20Q`47*ej6_@cIcre7#&FYyc3y>j5xjx$l0<;k}mMo)Q3{ zUlk&}=Cfa~U+MLZzv;ifMxm-s*F9|ZnE;2#A3LEs+*{z2gXO9WsN0l%UO00fAY1(XQ>I|{)70ALXkA^y&j zBq#mOgL7~K0LjV!$`|}4%K`!w0RM<)US0rze?=;=?7vWD1+j4Rf;f0VY$PmPysW?8 z|FQ0$$mADV|D=Bq_y>W15cmgye-QZp7lD^uNOx;@cV}K^W=A(B6LTk13nnus2WD>* zXJ!^A5Ho;Z$lKY(%+A7{#MHvd#!-O!qPvfp#Kv5JT8l#gq~I)OVQnMr>uRCqtEg_~ zYiGt|PAwz|$M4PS?cnTS;ci0W?O^Zd#_KIW{aZQjYyMX?GxaNztGOkws<`BzEUzU2 z>OXDq^73NxVq>L)^m6%+Mm!GB-#;b;D3_G{&D_TPm+`T3dui?Tl$|IN1li@mR4{cZ4n?1w*$eP!bn z`(>?#`0FM3E8t@UaWI0|)c*?jm^e5={LKH2?-i!scK+{dv3Olr`_IPwqWm5ee^~RY z$g8bt;pSxT@e6xFE>?c#|55o1tV(Q~=;YQ-& zXl`L?<7i<{Vs7GY@~2&Y@%=3*@rsS6g{#{ySbpV6{w$ENGO_$A^Y1Kw%l+zCbsGl@ zC0Co*r_IFvcOU<^p#D$!|H1k{l!(9TaJTWbF#lg@`4{isYScVT)hx{Zi!~gaf8P8* z(Efwmzm)z5!G96|mXx$NakEyo@%{_Bzsi53_v(`VG~h4NU#x%Y5OcD3a#efvSr&pC zj%FtIHm0txn4147_HWd`CI0HX|9vO_r^o4!x>tAh>e!h7c7Lyhza6NBEhJlIkO9>GCRR#hQ77`K`0}&n(65D3Vay` z5C8yR;IFIgzZTQ~NRUwA5YS*i0L&{D=NIMQXs;YVFmQ;M4FCc-5CDb(j`CU|zK`%( z3shZsaKNQQIj2dgv=mu|=xFawIcI1`$_&Q3YAIlNL{iBZy}iHcm%US4+M|cddG$@f zP51MxZ4T0jeuq;gaJhReKSQy{7p9Xj#x~4>6}Bh6%hhsBP8$%_q8cwcvgATRLsF-eCfT{T{uwGQz08fiisJz;kjN?v4?Cbre(-j@*o zO&tVxDf^C<5-z0)?|odN^3~=2Y(&=fBXMiK<0A&)OfQ^Fvv<;wy(X4A5|Arwc0JGb ztc3~qh48b0e9ztO<|Ga}o1`Khvj5GSD|FGQcHU=2w%`!m-zo*~=p!wo&bX@q{v zss%d(Mje=~=91l90DVl7ofPl;#0P2hqJZYvYba&cGdxH(a8n#|;nC0-y&2a#Yx#lu zDU4}^m^mTy+NF=k`t{abkmcObs_AkT4wAU~r9+6Py#PF(EXdspEJ5GBFl}UITZIgr z$xni$I=Z)yUsd#AgbM|$RZKc^Q`bk>WLeeN*cZ$fC0*Be(b>a2jB#aU$e7u*DDw6- z?!;-*5TaY66h{`_cHJ|(oXm7kPX;dE2j4mPA6NP*d$MyW`;vV1j)-I@fqomxekcnl zHh@9~45={2gscmn6+k7|(3<%lB{nTEns(3787I(dWpVcbkAzQ`uuB~Z;ON6lI&Dluk@ z4BreNgK2!BtrX8}mZRi~YcgKi=;Svwg(H^4bgi&sr@Aj#%zJ&%X9eoi8p+EjXvIi+1QWgrq^+@X%B(HOFw}Ch(w3AczeNGaI+!FMvE&bo6EH&rWU?!W zl4XM0j%mBWEIfR+D-ufNps-Zvm?i59aRyr)!X^|Ej2oc*%*b`^0i|1UVVBK{Di-)G z(v157TfK6K-hLY0@gg(L?>X{e+ubDFsj7&$wBx%IF%o6K~Dz_n#-u%=(7HR#oDh~HnW zPQCzS6`Le>yA=Rp9*beNEy+AsP+1$!ADk5)((Iy^t}!VDyK82`j11LfhO#BcSOKGB zOCscE$69)p@1?)!yBd=uY(<8Dc>z#uuAZf;y$S#JF*|-r2H%@DUbprGmVmx^L9^Tb z3m`x-!9%!Efg=^BMl#|(Ybo&jp%1W zvlu7JSQEAMwNyymD{9`kSZ6$AnQJm&j=NIHp|OQ*eK~nUM+N;|f>6sfBTkZo z`Iq8khaa@LB=H{`@daEqK$1;bjq2Z5i0>fUIk$Ns44_rW8?0y=%s!i6tc4o5^Wy z9m00l;;hxMz)U#FV{2)K zmLa8Gx36BDD>iEz_pNTfmh|{4Wa?^V?%K9~dw_}m?#(+|DqjVOr_V*RY$>SeZI~yL z_11cP@vTz`p{~GCrl2s+kCQr$fn}Uz7XAq%?p3QAH*ET7%U@FJ`6kzvn|!LYHZ;ZcZk*lk8Uc0R^@=obTkyfoun1ZobV{u(4!?ec zYY(BABT=5Qc~0^4Fp%N3#kg2&+*3bS3GFTI>WX;aK)z)wnLN*)Zh*h$1qHVn<_+Vf z+pLS%?XU{!CXAhI^!VcAa6kmdNf%7vo7n4we?SiFeCieUTQSlb!d9SaVDxa|Zj?zBVlm|?HzQwcKoM6FpQYCb3+gb`3x!b3hV*0Z z`z)ZDe?5EU2g=bTdI6w+FuJzm^89%2kLRzZ;OSuZZKdp!xVV4rRMBusm8X)K?dXt^ z;T_)jYGmsIeG9^1&BW+R;uv+}vqh3X6ZNLYszCCMM~*S5b@7BSp=m_I@j_sgUb)sG zUDYpFzOgjvgDvq-KCHI%N?q2D&9A_UI|d;30E@~$fbmVBlZq>Yr9<$ z#TYk|NGqj5F3LhK7I$MUFMyAkT^y(1)ZpM!81rvex7d|+@3}ub5WZdi%GvT+_XXf~ zG3GGIV53YfH${;lCwY-i!(*N1gS-2rY@ff`Y$0g2D(&J%vXj{>#X+wx!-R|Hb zIZ8x0sCFx5$bSU%fd(T?|6DXX{>it3GX z-0Ya(WXt6&qHMz)KhWc-)M-9*=aTWDSAsrQgYK*vXtdP=`^XX2#ssVB_h~dDN&TUX zBj%{p#Xv+nXPys9bwnfr{K!j3J4>$OVPgvRFvllIHEGSTV+|wV{U!+`y<>*99_>X4zkzD-WWxB>M-7$yjro9YrZ~st z$9L2}FyVjlk-?e1fv#5#`?;Ed#lcITESI2u{=n{G&^MXapFe?m+>hNK2eNy{3Vi+P zq(pt)aREYrfdPLVFZrE-s3>4)B%~}VAYo;2bP;2h-#?tM0kaTrgtnGZUKe@8aN_I* zfH-(2E^qDYJ&2k>lNoC6L}| zbd7wEk@?~T%ltT5p6tEXP=6MSIH%*BPPw*iGJXOj`rf_6(4nNL=#We2k#i4i%hf=$ z^Q5X}YA)YCD37|hMcv_6lM8aal0mH(9VL`{fEQIR`{px7ALhdbAAbt+ZaxJyRK>+w zM@J|r_F8A@lNUPr?z8+zFH9&ULSlo}n-!A4JVV(jn=u5?sD?jv^D1m)7bwaj!QgT8kVIj1m86iVB*7+J`I9br zgCuEnrBqlQ=Y`Lqw6E!wNbUrX<@Jt(b*UC3RI-wik5h8koo!;oT%`uIkS)_kR8*6r z5u`~IB{Iv-lj--w4Kn*vOiM`@6V`6k7vuEq&6VVJC9E+ikSmKr$i-zMSK1w6O_fTE zV7~G?FpQ^)vJgKLDhEskc-Nv@wD0Tdnm9)~{QrY~}webmzM z&fwLLL1Sj!iaihMJ?}*hF*qbP?q)5EwZyfdF>lA2NS)wxEf=dM;e4p2FIU6AE_4wH zQ&@%-1WZK{BugEL?h4-pyLYH)Urxwyp&S^P*xV?(5lUW2s=e8Wo|V}SHj?(M6OVIbUhPH>>3$4)cTRH$Ai%bHCo_3xm>JI=F5rpqU~D3R3f z6|4q19~B-d)j#Bk>9bzYjnNa#Tf5DulwW5>q+0T*kZAOpR`=A~ouy14#HEs);vC=) zy@}i2Nb^5qomv=^DCgQ?LT=;a3BW$Ww9F>aYZ1{tim?uuC)%w-pF+N8Bsr3WzTU)fCXwr#s>!f_m6Z3dvdzwRt) zpD!SsQLG%*Cl@Bq`{=vbnfFN~`@lb6CHV!Q`rQZf!sv;vbNGj*&I@22Ysk%V zvE(>(i7(oJN!U&Qz}!yy<~T;I+j6l*-r~E;kV=709>HYEYKTSua4>ir3?IoeX*E(I zzN6XOEo^}`-Ynjj4FX4N+_KLbTDsPm2tCWo5N2SDHg0V&yg5r8vqN6HUeH@kF93D6 zr$Y^Iy3-F_g&L4v`iCOu?==iEgcHoWYtZ~7zAGSL6I04q>}D^n*tl?>`H1q%c-t>l z@NMWBNOYESMY$!6Bq@3lg$Oipi1I6UKV+DL>mylgEC9w&8H;eN+7_>7ClBfdrJa`9 zkKctQ^O4_sDikaqm0)cTWMQblNOQH-ovsLXMt=>{YFJSXo((gnL1D6Z14gwPj#Tv- z{ceJ@s46{K^s5RGGyQ94CcOew)On@b#5zacDQ)v0X0c?PnWubbTTea3cq4LP^Ee_= zV4=h!U+nD((O3pDz_GF}F_#~48bp7UJV6Ro;5?$@j&d;xTc|(deGA`G%gf`b)V#oQ z5zI-{Qb@N@Fa9OsUQMZJ`X^S4{#@rLcr7pjzPeHmiPChnDyT1ll_dyWu?ue7^O99H zCGyXyIwPF#f)7`;+%J}|8nTFP28TvIufUKE5z!E6U0A%%sh-?%F_`@x*)>c|MnzB( zpyarj9Eh0i6U5f4q8cV2oKx`4_K>W%p1DX>>>jCcjAkymnHJK@laFil- zue!Tsj~eDi`q1BIv)K6(^HM8>jg3EBPrE?VKS%G03dGIJYl6P+qq<|R%J~ajb6w}eb3tiEBt+aR{v;v9oDa;jpd-{Uzq+J2 z{;`0u%Xgq*7aFI|?Rrtfxcin@h3AURK=f-D3TveT68Xfw$A#3EpbX(Rk(vdqt^jJ= zst4vk;kfD6k3=!=S_yrtH@79EHj&D5Xi80LtU5^+I6j@1@edVlvEfIYgA~22SI>_M z)3FWL**e@6hfR!k14=`tM=!p!I7tu*#gFgv#KZ@rYR3kic!xy4HNU5DbnyvVAh6pS zNqZ1znb`?>u2d7lw&BBU$G+`Ryygy{KeJy0E%)E6Eu5Oo$v2 z1$e_Klx5tcnU7XV35dwzqpmtsCX*7Z`@a|0hj-Js%pitK`vp}oFJH#J0B8jrjb8wx zKL!J6>+h`hN_S$n4kaj(cFpV~RPpLB<20lerZjZ~yjrnIi^YWbqbMM%7Pepfe+ZCza(=!cjg^EXJ58`z$c&MDj4i`%=Ur;*_ z0UKZ1te+}+)NDEbvIBT#ZmO{rC98@+IcN6rH*Ku5TVA`e)=YGAU=FHB?R!;T8{dKp_g36n*v!0Mrdjuw7il<+LOk zgAX(&#;R%r?Cud!JezBW&>OlzE9ZDR+y}2Tp+AVxCFDF6n~6mXYC60Tvegx(K88&)_TCjBZdzS8!`aBl?VuOb&g!J19PxK#rkV2WP z(39*523CP9 z*Lh}vA2(coywF#>vdTvcsc{g@PTku)o$IU>nnX;mU5z=O%yVElWnmlbe5IL?|v-bQk&k7jxw;#_}rqMR7LAGb2%TkCrp{I-EzS=n_Sj5_O++H zk2PumYDp^NG-jkA>?KO0bwTQmwrp+^D!Yi;%>i{kd~&HsTU5KTmYvr6sOTeZ8Te)! z-Jhp9-IR8I*XHQpo-0smD{9jHm4edYHu@>}Q(kdUhrGEFy|zMpuiIvat==C+dX;a{#! zkVra!M~#fN#I>y?4!m^WTERwGBEMF&=IyHJ@n!fT$|0)kv+p1nNnF)BMh)p$Vu?w# zQTzg+L}9OmDLgKCm%XGhN1ASy%(G@N&=E5g-V7;inV_Nq{TScT4wTDPA2 zky5wC(Z_YxAx5gdgxfUIEaISnIW>XCCF$@@CB6{KdP`$vqe|&eC0i?vLu)qro%(`3 zm3E}T7E7{#T0=7YKJ}ZlM%RTEZGGh{xU}$Wu0(^RLivt2NjA8cW)ET3=$F?qd%Ql( z-q^39J{8JqX#YByeBJc2TpT&Kqb8Q>gz8oc6Lcf4`%UwNnDOpjkqkdpVfL|XLp#VG;-?+HB9*M`8Dr6R zYBA{4q2nh-Kqtl{zuz9TPy2)xhZINQynJn{=jLCE*fO8_5aXeXnEXiS+?XstHE*l= zT_fNy!#-X$tNDvi(T}NVor(BySUvRS4N|B|1|B0DF_^;JnXlPzq7ul!!ck-wN1yel zAj{lvwHCEKC`)c8Xina^q7Zkw=LS8f|!xPI>oR+92!q|9u_C_I_@Mnh&J8#iKf zOx^5`tCv8Duqo_~alcI2L`jp#T3wZp+Mi`w^F8V?%KDLXieS9N%<^n(xM_LZS?l=` zDsqiyS^I=$AEn!&+@ZO^$BxX~gHxRqYU9~=NMCU81u!JkFE0#+mq)I8 zHr#v|wI8*xSytnp6vR@NhBMMH@if$h4x_eBm+QL+ZufrbZV#>2`11x(FXT(&0~IZF z^Znc8)uxTN5u;k<13X!KX^(0Zf5 zS5cE1N8j6Oy*n9DLO$};(uW(SQp7CQ%@7??SztA$+xPUJL;r&H`9rVGSu5J;Txn~| z5b`&3;r1>K76LxNWAR={Kk01_)z7ZCxAo{M?08k190Zp`sTHRP4^NwD>r?hK_i73q zCS)x5CY$XIPvZolg@~=*S1dQ8YlO7>k42?|SU+)fj)0XB5ZWwkrkwgl5ao^yRlTk; zWBhh87E-$rS<@LUsSf>h!mRt(TW{B{!ixK!IFawt z{?y0MsC8^)2cLKvrm2t45s3#nOxBH*TjjhXe%o?i(Fu1QC|yDzqW17M+LLtATw#{* z3Yk3uzhCYYNe$gc0>swv!!SW8c4y3<77s{)+7Gxq*8vS1SJi715?Wx_E_VY$2f zCuvKR(R7FE>K!PU(8FAwYbj(i0&mSa$YFed6OA{4y>LV%H|@i;iW)S+#5D~ZUor0+ zQ=T`Pev_e$*CHw3y`Z<-(>*JSit}VGA}zlpAbQb(RXuikSR4}7|FkL{8Kt+3pV0bT zt<(ddP1MXZ>vO@kk8*7vTPwx9=@93Wc6bH7)4hR<`aRT)E3%*NstH`DigdZbAT>BA za=AGuHmb9Z6=cW$EJ_8P*#O>-%X7zAp-WCeQf>LB^qD0XSE8sfyz$Nh3kCF%aGD;( zmb73?0CbCT09}kb-$2KD^WA0?PK=v=SY4ynCRBvY4~F_qBq@7;$K#(`? zf%ZGv32y}K-WyUQqK_d{AuWCWO!Bt^_{M~|3W@M!EsCg{b>9k1z8ER{QKCnNZHGc` z?hfEO@IxGkwD_?S2;$rx$e#)*CR=t#!1wiRHcLEWtL&n(MZd+KHR!8r_eE8Pi0+1t z6GYi5i=1ta>n1=;JwXCnH5}=;!#8RnA{`xG?K1E9NroOv zt3=r78|fkr<4JG+v-QU`8Ncmr(YYJtx+~W?Z8=C!l`(u4KJ`Zu*Cc3zB>NK6?TRf# zK4Yk|!ZLURzr*ShqGugJ;F39Ow3O8NIPP z>{puJreP&6GIAHhKxggAllk4Jy!Tf&-Y#Fj(YDnNGM3jeD$jSUO^XZ#1e*ib2m*|c zwUG9o90e$uG2?=v!%+#`06&*UN&4MR&>qX)Og-a62n0nM&Q@RfSbX3>Z#G;YR8hRw0_ zO~;;QU@uOqC#Hu~%P{(TMA?sI7fslk-xwe2mmB(dIR&Zb!NUC%qfY#=uc;qDoI%!S zeq&ac?IB7&|57{tqX3ZyIRRy@HaE$+<{Zcd;c*1v|D3L0Z~3~qeYOz75RnnN%l$sYK6SDF zWEfF}Q68Bfa5|CFO2xlC%iRVJmq=-UTP3K)+EOAy8fz_fK_nUuJbUUY))4oWhJFOHu~rKnZt z>>3t!jVm41R-V@!xV~NJ4vwaae_iqLHE$)ha=zi-Vv+fReGX;6N5q9b)@AhQ{s}$5 z2YonWT>i2S`x&P)ANmO9y%YE<|JS&)EQhP2XNrJS()_RT8_xKbR0**2SwAyU^dC_y zTa&)KgOoo>m2oyysn?o1`%=C8_=W!ad&GifNGgu31kI2s;%-E!g>3>wqa*u``f8NI zYm#V3N3FkS?MmFqb-D1$`M z_b$z5LU-VtyK;^tgxrcXCu69G@Zjz%mZ z;q|L~`0a~FFe_H&*O_?h2s_NsE+_HdtAo@y#8{siTL;By!wo5oIvEM37&?C(wPPnk}y_#hE z5!HhrH?mDw5Q^z16*;xoeRN2}0Bx|PH>L9H?g|vRpbu`^$NjA^^<(f4L%ishREbr30V*$C$;E+Y=Hq{Y^KvT?j zP-e0U!iSDdRDr zH#E3oo9gw*XvNX%Ez#wO5Ar)6$D*!N+^{YiTZ6@e*mp`Xr5nC%CkV*2Ic4L1|$Ndjh=kmh_pc2c(q9`mzjiWyooM=d1Ls%BO#(6zb}9TFZ2#K4L1|o# zj_?<+D+;oHYP^qUHJf)=~q{)CWB+1((wh*%JNjT;B< zitKmP3`r;;DO^%<&o85!5yBe?oYX6vMf-AQqx;jbUoO<3%g2N$ zoP3g{=R|p*_;EpQL9oGW1z- zaWQ%D=@x=Gpxx#3?;5iT(TwfawJg#scdcW>!3m6WV7% z>pD}EA=!)4xmS$F|AUS7rxse$-Y#jXF8;%lU!;6}1Ss@Yk75viL?$3BG^{8Y8y%w} z5P2tj>Vbk;?0Mcqjx7Z%hcexRjT_u_N5P zW1mxO;2!RUqDYpZfRnY49*BeMj!Ona(xK{P9tPqjf@Ft#lmw6^8!xB)fx`NbagUVA z$OkvZ?<725=YL9kS%#dLb;2@90XK-*aakAgyc#B!10Tl0{U`}MQfQEmIZVgD>2w{K zIYH^tYw~jbdL(l>eok?$zkk+pVlr+a&lbwZq|G#ufI3lIc#1RV$7J`AXex4^Shu?^ zjrDo6#&<8^{u6sDGt$A9^2)I_~^}zKB7`}$Q zT?YR|yu>-ps3D3g8}7F!n|AB8LI%>r=DobVFrVn7E>En0Es;0I^ej@)al@snKH+W1 zhYc6X@B7o&?`AnYrhOSc=R=%wElM#jH<}EGgYU)`W@uE##ImKIX+Ss(sG^LZa-oP7rAr+b9GDV|Q>4xJ~9 zAD7q%SB_hO{S*9c3r<_lIuB4j)0eQbhvV)qfQgaf1D~tf9OvYfUM|K(ze~NX&Yx@K zTIMMur8#Ty&wO8o#w?mQ=hSWncJj{0o-fj01A!%r`i|#tnf)Al%5MQmIy>zLF8d4_ z_4w%*njhn5+7U9$o%Jb*ICneT4!m-V_u%UZ_4x1gFDmwj`e2EOiKl!%k9{9K96l1s zV}r?bz36`pqWv>E;+aamJ0G@u)jrJdPxqmsjQ3~t`O5E*$L7z^ygpiho% z@De?KJjkNHjqtwWCgwp!*8P5>mJ#>~!8?0gJdE}bBA_|&3=X0MMbOlR#v@f~0J@yu z7|zST{w7bZ{ucelhZVnGY@sJLo^=v>6PFuxRBst4A3YN5N6Qt35E7e!4z^%t5By3p zUP8yS$^ISS*?4QGAg;NaBO3pSZIgIk=%^8ol?UNR23vS~+6`^7*+QM`Cz1zWG@8hi z+)TNn!4SeP5Z6q$1^(z)Z)L|>RHhY+a~(y@wKSo(r9{ACnz61f&8G%owYWSk$m7G! zgeaRy`GBg+WVCGLM3>3yC=%cegyuURO~q0CBBpBWDFC7$m2X`Latt5N^bST?QZazH zf^2<#BZ#L2(K$d2jDzm0glv7~3v;1aG*^`47*gXhEAXLyX`zYrP(C=y%{z}#5{i(@ z50MFXxm@D-00v=G!FLB&45*L?P`Szp%~ovd$V#KS>)=V#+$^a4X$>|Ha}U(t-)5+> z@v+~UkY{m3A<>#GQ>kLlw!b+9|Hduf#R?w)Il36NKxI1@s5m+-I0pHtKcJBeg_wJc`w(|N4Iy)`| zG0|T)-NHwX`Qk;^Bl1-#kSuXe8~hlv2&;Kl2Eu7#Q|9K6;3P zhD%LK)_>f}&pTZ8jiPrUiHLX~Nsb1+83H<1fg%--2X7Be77AnpBtg&9U#9;;`Pl>F zQ--SFP@5*>@FaQ0J{%Bt9ISZ4B>#fAMjT-yJY?EyTb80&WTGgLTcx4{7X#n=3K!~# zY^aZSTbdq`l#naLlr^&QoOl$|G!=TsEn51Cx?ya;LylTk{KN4+)Z!c0VumXC$-q{= z23U0n?6L;u1z1saN0)1QMl!^_OG*>e;;^RxP~(v}3n+>7@UaIU<)I7LZM{dl;z&75 zH6Hfl?M}3suDZoSaV#wF!t3NX6WxDzDzc@U4QVZ3O2v{4AYWh;u*nS ztrPnurH@_Y+f5iz?>3~PT}C(ML1XJgW7x8F$gJX;ci9t1)x)@2CRUJ?E$RiuWvFqf z5*sSv?mf^%bUGeK=7)JJ9W@yT;*NN+CivtBpXY$2AybBzg;B zXws|)5Xzu1xcfYPZhLL%w(!5#pb@eOXxsy$L#^lXxi#VHG@AQfm(9DWERPyR8q3f<%z zE01s&>UTWQ+qxtTULcf{Ty?&00yk`Ob>Ssdv@`cSB)v2n)IM!;@E9Rx4OMu}Hv4tB z*+G%7qN`;UIJtTv@irAcUZlKbHbvZ3e6J2UIGqx24Y4ew>EzafE4M2hiMpKkU?QH6 zh)_Ajo1%VaORE@G9&^!ekRQ26nK*#Er_>inNe1ZU;^__`dtnYNDqmWG4%+I}c!-{s zPwJmPy`ET&H-%uKvd%LW*GT>Tt=`9LjEx-RZtKeM_3dguN1aYMmhgAb*9ZeL_GBIpslP_ z4S$EtoIS#pocUH&RyHZc2T<((EZJ_VHATV{Mdr+MN45FYh}n-))-SY&X-Sqa6g;4W zB!Qj5_^|r0H#?>8ScwKAC@5-B`meil%?FD@`V*R(%t@rf{Z3qvn1uO=I(x1_s5Fw| z*iug0cHKVklX4F()~-{xfziV)j@r;qgN?kfcXvBlksJ}UPbR1}=*otcf}jjo1r5y0 zK%8!^!K8F|umFN(R{@#2q#}8A<6_yAIp?u_AJfUq^yUK%#X%qE5*7&v(l%rA!A$Na z5sn4WRM*hr&O44nO_C0E6=L-LUrmA#b?3WY-Gj5=f*6^K$6)V6SMiSCIY?A-Z z3}a^AKZ!Ub$uiwvbVxAd!y%4><0)NWJ}woCIat=E)XtF9dj@LPZycHiS^Z=d;!*ED?PrmsEaHJADi)c5OH-2d5pUwjtDLz~(#C+RJQ}#A#Rq3o zSU^GwesD^)F)a1RWQEN+YpT#nHC!wc(IDA(C2#Xs^v2}Lfa5uMGoM|wc)*Q5gT+ro z0lER@JOLk&2IED~B3N+Tg~OweeqdsXG^Db~3~EdbN?Xv?1ntwf+d`^0 z$(h(bl(L1i`W-%E0IO;xMj&Dy$3Ff6i5Lfc)*W<>qDdO+r98oO zFKC`?p6h(yl8a5cb-es43W>R?amlEM%^?ZLAA#Y0;Qm`>QNUJt+)w9)3WAKflTWKE z=lS7ENSkmz!|z)7JAgPAy4LrsOC~?iE)hxL{8yR%jgv=&C#E!=zH>z+j@ zbp1~TE>aK&MsJ}}z8;(Ot;uPX;ah~7-E&TnHIKFzeO@3Oy-|;pZheT_XD<5@KuC=1wmRsWl|>$b zYOJZYEsq@8-5dA069-zR%WE#-`2h!&5Bu6ACQ>!R@G|kOMI%ME$ZW4mzm9QQyxEiw zG6kHv>%sb@W4*`V+9*E;O8n`YZ*i(cEqF?dEnbld3g(-24W1id7-7V-sP*B(wm+!9 zl4HUg^8P442zPc2WjjGb|8LbF2G$=n` zhj17UZVq+LlFim-1Qe)9yJ%&OGsWQ-Rl$EI2+T3JJFXOT# z&hH7+09x$DPsCErlniI0F8Y81*xZm!@oy#wsFeBLNDr3z(=`n1neMA^Z|R6j;Z-snG`UmQ=S_2Y2G8wu%fN0!6X z#W(Iy0Lh5A*$3TZ)d{Crzbs}aH+|)~J%z+bBa<%Sl1>Sm$%!iBB_+B_#aUQN_@0Y7 zm|mG2`VdRjs5x9hw&$_R5u4~F%dmoL{tb}=eeojcu7qa83!uBg)^_|SE4UQ} ze@2Pt8>=6E=aJ7SsGsgfM8yu^1Arq&tVq75<;Fk&-}9S;dsx*njJ&6DO563AV)35c z_$bz}g*0dsd){1f)Z(UZiG(gvsewJD_?Y4xU%9`>4J1NQ4&yO?x=IeROy;=Vz63M5 zAuEv?wz(h_jz(!%)!ym9JWrBTKZxJv9@fQ|P^MBU#=n}Bl-`h7HB?nCbWhAP?OI12p%2d*01aL8afPMVUb(=#532fAzCgQS_+Cgm&<1xdJ_#GM}-O zNBx#KC2M&_sS@h z7TnX!rJt-|u0R3});EXPk`k2#OA2AS3BgZ6VeNEf*R` zDH>%{IOu*fOq*tNdM8zkM;z@jzsFd3Sf{h|aYTAY`a0x!2Ppe<0fA9gS@>rFtC*(Z z5>7zUdWLZ3HQ3M kW%Y*>oj2?~p-Bs4T4<;rdhC3+f$R89!fpL_;LG~|0ia;`82|tP literal 0 HcmV?d00001 diff --git a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html new file mode 100644 index 00000000000..1be7b71c811 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html @@ -0,0 +1,125 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.jpg b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2befd150259b498873303784271f56a88228343 GIT binary patch literal 22837 zcmeFYb9iM-*Dtzan;mt~v2CN%v5k&x+qP}nwr$%^ckHBd(!KY)zwbNuJomZx@3Sjw zX4R;HHEOI`zh}%*wceNCw*iP^BHu&+KtMnM(T_jC`wrrzu(OFF03ay|pa1{>kN^lE zECAR?59s641;YNPZwf>P0QoZx{K4b|eEbK%eO$IdCIHYs;{eFQ^o&1xSRb_aDGmVQ z!yxQOKlS7K-9A3?5C6}*gb5YCxQ?ZfrHQ2x03ZP{0Z;+(0pb81fF;1_<1+d44glbs zerQ4dR{yvC8-af#@NWeEjljPV_%{OoS4IFd8t^--0D#gl(9>oAr%z8umksn+cJlrX z0Q^_x0lEG=O8;gMzoYPP`!@prM&REF{2PIPBk+H31m1T*PL1vDtvP6EE$wJ@^sRIa zX!NWsXq|PeY3XU`XaQWj&el45W(M~7x&}rjmfWNlZC#}JCi>i@%FL2Kk&% z35onI@B%nd9IEbZ++rbhqU1AKfgeLW66TLT?? zE8E}XU-sk;Z2k~`QRXH*|GhTDMf=3xoH1~vA+cWn{WRw{u+D~o&NFo ze|!pK<)l?f>xpk(SZX*Z-)2 z@i5S_(BbR4*c;g4J6P%)7@Alb=;Q0_*z5f5*MH>xQ561&jgo<_-S4pc?&JSW;4{$B z|E=@iMgGwfG1sv(mN9YukM4iyfAl{V!r%J;Vf{z+9~**J=2o`yA1lRxN6}JG$J|8M zR>$5z|8KSb#{Ea*Kkom3pXUGRhWcylV-bBUC)$4&-3Rf{vNf>$SgW?b7v=jJKo9@| z4E+1`(Lg_5V31&7prByT5D?&yu+XruFwihCaPY{8aPUa*FffSdh)5`?XlQ7#2pE{? zsF=v8XsEx90D*kyfPz7RfkC0d!N8&Z|8DQS03=92B47su2nhg;1O$Qv^xhA^0RTQc z`>4hK(?G$1K>$GD5RkvQK!D%O|HcLa27JumH+K~P3-ZB61VQ{LsNQ{^)Or!I;Ez9I z2_Klr91v=I7Ff%ZIZj<41~DE#gJ3DDam>&$UMOM4wlduC0!=Jo21H&rboc|;&O zy_kFl2+VUZe$h)pDd#$q1#jHGXy?53$?no!?)4X8~#+MOv z%c_H^w#$~tMu(r0^8AGL{_%AaM;qG79cmqENv12lDkY`_^#)d9V8t=Sq=LC++Jaw3 zzJPyNY(R;)P3OgariH_>qc}1{BR7n%(-Bjl^jUsOcv?CJ(RA1E{jO%(=4*<1*uR8_ zXm1Mr2DFt6yGkmvpQ-cX^&JrF0W-E_T|5?V3>1zwLU2jFL&fse^At8K9J=9#VX_P! z;h3vZyobS_DWO90El@v(1a|a+cj~kD?j>Gca^tjaSVV6Bj81Zv3RHIarzOpT&b`-x zQw9mfFS%oi=Ie?~pQOx6Kd2EpiD`X(|UTRSMgv*w$Z+pC&`mMBhcg|ro1Yfiq5Jn+qT zTHHyq(K->J?V}U5R*k`ceMuM!S#UWhhmuBekPj^GUrgp3cLHlnQ2v^nh&VlSXJj+0 zj(~nQVdi;Qn2zOrUZ+hL-DswA10ii%I5vN!!%m<4NyVHl)xV-2HZ?r9k)gzv__DjM z9y(cN>Gs}1V`l02*^{f>wZEx&{rZWflB7jCiR41GcS8+rX>qjh(zFhISc8xZwlaY} zazDL}p9*aW)*C-2B?N9(Z+*!SL2zN?N^8q$_BDxCuN&gR(_4MkD)DH9;x@^O$vZZ? z?YYA4(A{LRLNT>3RBR$yH(=nKFW3WkPb;>dXp*>*p2e%?TALIEotIcf1P+bVlqe>y z#mK57yN(T$omt!3EpO71SLU@svR*~_7Xo6~X5wh$N&y*C7zo`Bb2bd{1A#(U^Bl=W z?iI<#7{C|>SW;;}H+ZOw!<{oT8f=0BqLZm;=t!`xavj6B?g2H}abX`uNAkiQOHBc_ zGt|^EEg&ckxlNP8dK>L9Lg3_P)cr~zC9aC_e19VcBbSm&k^otOb1z(j`l0b4+zKpT9R78Fy86_lDuE#c~1`MO~goewSr zNNdr%GkCIJBvSQ$zexFdZ>_o%XAnTy#px#;<1a(6JD)Y@{>y`9*d16Cd3+}$LU)&m@^Q@?Ta-`&DrPEnV-V`p}q^cQ&G#vw1?1UItZ~W}l9}N*H zmV|#x0~qMa2m?~Ys)|)zWJYS>^l{WfYov@0D=7810veU2Og6OLi!A|EM>)9}p1)$w zC@`GFAFiwr5V_Q3wxm3Oe?LVDtB0L%8a+EPUUX<{O^3V(Dg~D(X?Wd(hpA0Q?XLxF z)zzT&U;P?i_Yo1KyNGmDp5PrN)26C1L<6;zNBNO?lWu2m{gt~`jF1_DK)}p`k%eLi zkX)6~RpV;Y=)p4$GJ+eMx4{@*=>}9|jR8~uAI=@>w8nyiuOX>T;u(q7e$zVPMKHIO zPhBhJ6$a3qGM)_FM6eC`3 z0m(9Cg~_BuWsckt(xbP7>9rW%kVAT<*^}Arj%Y%^ozadMU?_U~qN6^d52KAbAs1 z119>82pShk)qyG*&6Esul8g^SGQQz+#F|Zau-;m&nF;Qv{=#2e$GRU#S|C*xzLaj8 z@7le|pPM)77-HHYapjH%p3`c#F!Yuf_m?_-BgUn38l8l^p4baD?UVV6JatD1@>)s0;!&K16hv zayd0UJAaVMxmw zI0_Zdb9}*CXi&V+(+8A;UWaNJ7+lsyF%Ox~rgrN$cG;;Sm-41j`zREc7j2L#RrG$X zu?6K%a#APsYx&4*T4rx^rWv0%ddsgqvp(C{6g*eTxCMa;J!R$xesyYf!|5|}=z)I+ z$nK~XE=w2cHTOQNqNJ)$32qWu?&inL+A4EwXo8f>q-~32R3asZbuTmz&}ZLwQyn zh0}mRpwBT!@h6HY=0UsLSp?=<^L(~d@sW(^4ogX2bZ+`LzkmoB zKjG2X>~z-Bef$Sj-_DXN*AV%~`0K21)yg!iWI2^+3H1l?NhkuRCNZi<_ z_kK((7K=t9wpB0#da?*6gH2iaR1apt*MJgSstu$?B$08&gUaj#GXnoye%lSTj)luf zZuH?=F-l!lNmv!QIz<-y%7egrB(B$t4;>dnxwdum3zOz(QGXsBAyq<~E&Z8U&dd=1 z?D2{)dsu7Kz=rqX4r9N`){`o9tJj@BBoPesp7;wXHcW!1g3-Wb>cp_X%!Xqx(pLO& zDp>Ebxj(A_lis}sWf|2mQ|+pQStk-Jf4{YYV%&J{V~iALmC#2I*RoeloP~rGCMf} zLK@Bt`@#mP zXOW3h=*XA~bhi$%u)z{-$`mT$zCBR)wReELVO~i~k;@xW&Ro2Rtm*Q0xYfOo+sWuC zJoOHC^A=wy2%pCSRc`Wyv6P30gUWLdT-MP`ao>p{onJkw;_WQy$xr zKnS?}nPcBUKb4XeLt$Pl9S-RO-HTPi;aG5#g0hY83plkX zCRnNb!!tQzlY6-vO2pu68Jv{xGEuf`^<@>qQa520xi1wNEV)#^?&@(wc5t_j3`nfX z$BjN{Zqx&?*VW#mKpLPg1lIvkWLN$SJ}v6kVU<<-xb}NX9_$+SfJ&@Nzobt>Nv3-` z`cP02Pf}#KwEDt>{yuN9sZ5XRG+|Vo^ODm~e7R?o8=y!I)1yDjEA1PbW>>vF;D-4( z+yH}uf!a01IsYtp>LDBdCTZ0xSR8(b+pdG}{FnS;+~$H14#8CvIbbJ0K8&DM2jJie8OW z)-1Zw)lS@LzoxFl%X-aKJ!?dXi)AZ_ksADLtAtM{716$YRHiq9NPa)fa@wK4iRD<% z7@Jf*>Pkw-@)02ABbSR^)?$EV!4ch#Wt6*{opG zY!07H4lasV{1Q_X?Uxey071zP6jSsJ#O9xf35bPY4dUtn=i-f*W4Ed<9 z)xGR-c5dYsE>?px@j;f4Y0lH8LM!Ce;`HVPDU3#W37tA{W-qQXTxog6?G5+^_7??2rmxasKJcU)fR>IKM3B(ruXvLBo zWHJH?Yy$o$BB6OXR(>Z9?*POEo1XGYC;iWoya8_K3#L47dg+d5uSK&Z-&VAic}iy| z3+`k^f)}i}^fN3l;!=5uq2ck_%qaL~cmVtcvi?N541p*gw&lHZHrRJ^P)IRIsKyu? z3+Lpd{7N0nOwS#lqpoA{6xBwX>KcSi!A&@gq`HnhEhEG?DxZqwfv&oH6 zI~_L(5lG1&X9k-DHji$Ic`Gf0D(Gn@Y~jI4#2gpf;1QeOq*1P2zXJvY4la0Z1XQkW zm2q@#PY^}HM#IWv5}jjb%e1uAI_hmPDWc$u>`b|jl<`&t3@W;yay6c^y2fftWEZkB zhM`o^bG~&~6QQ8^-(-poL^0h#UvyW9k4R5#u=Y2yhkX<1Sg3*b;F@@SCcT1It{fgh zf?6n504UhlJISRAou-aSq=a?xh_$V-|H#CB2jG<^C@EPQet)=yrYC|rOlR#y0Zrzw zZj>%HW$kmJOP-#HFTX4>R7U^Nyrf%UK`B#`%qHOW42u)k+#3!-b5Zry?Gy*O~+=E_@Hi57s8#O4pIuCR{q-vq2LizO5B;4-=3v8-b zKM@7{+S;h>E8K|-p??u*3ZA!0`=^7M(QW?X$;2v#r<~x~f&Uy$snECwPJQ0`BTpbY zM%1en9sBdoG%tA(YX#Ti$dQOo_y?HvZZs`f=GkA+4mJ5+Uh%ph(gTa_6M{SzW4STn z-;%6++G(Lx-8HU-hQ60wp=G)hkv=6*ufN8-h;SS71lRN$X%MR6SU{^mS*GxLo&pGN z4&{H1QOf^{*@>YnSS-O690VlLj{LYXuAR$3IVTt>)i7 z@RTl7oJ3<#`#es1MAr|Mwx$_o+4^&M)JtH!5Q5bItn-I;qg}c@$Z?sf?Rks$yPB_1 zT%7yfGN4!@+b{FRgyX8lTS|}a-bRQu_zpTA3H>4sou*HLdj~8R*`2Y? zT0OclOSlrEOm}(`cNX!QEVd{6>cuZ=x9{$eaR6P)v^jwL1 ziIZB$JCQvwJ}fF)%4j{hN9;ub!&d3WzoRGrUJgmj_;> zCBjoRZEl4#01U77N+&w@2$aaTIOj;jhRqxd#pX19vBTQ93oIk~^D6Ms3ghpE*q~M8 z#C5d}@cMw2-q*A&iYb~)WtmpaP9E@x@%u5JEt7`4Gy3E%UI zwDW?3zIjZ()Pmc)(3EFNwrVz`*FNfBqt6cNvPA-Ysp}B9^6^Q)6;ODau^;k#lWef6hIavnl#mGV;!L^RrON*CN9+Vd2tBey3Ru z{yy2l^W{Z~)l3;~rIV%iEt_qHt2(`0UA4uhJ_#jf z`no4~lv}pB%W{l7I75QLZ^V#&ZZt(-YpIQ!uq}OxAD|VkPspGhJF?B^?-lage8wPW zIQ*8czWwa>(q9Fx;6U@UUKyH!R&+r@zOg47xLj8+R^E8!yi+XZDJer>>CCHOEOg83 zXJ1NcE20yW&zwv*82?$ykhl6A#5Opp!QRe$B}thqWf71)4IqdVOKHu9Z3a=} z=G}6lJp_!OE02d(kZon#^{S-^lbG6`a!~{$l;+=3TcnM61Ec8K@dnTndD9rMkJD7! ziLP}wcsfU&qfI6W+DNBs^Gu}AHkjinb=q)I1ue0jbHoG9q{lDfodhrSVBmdL+) zP>j2Ym%}me=6X2&B8+fuxFc7fB3je!Lg-FSpf^yfPhG6ZjP!d4;1~KReB?S4N=*>Q z8T{?vc7H6KU;DO}a?HEBO5dQB^#Y_@e+@TwACHeyA%M7bx#Ph*Hii8b#h!vtJauY z3MozmNV9LYXcWmN*f^D%ELo-bw1-wS&{tw>HBqzqed^}@=BbPG^`>FzPu#;YDj=NU zPCg0(zaP*r>uuEO$Xi^LAgQHo5!!ZG>* zwT=OMmOS^=fMvr=aVX<6uxM9@GLSBvDrASY4DU@IqQU4HN z2DSk5Y_UPVuz}o#-={|v5`WmLDgzndLcADG>PVQEs9BC}nLq1H(|kTjCJ<#{ zl{A?c4q`bhG0aHxgniWOMMYq>$M?OM&w7qCVJ(a#nz56hub6+3s$)7aA*-1RdV`V4 z-Nv~%lrjN|oI5TJOpXCp6wxvktW1`U%#+7}o3fXT3$W*d+R*td5OOOim4A&&@#0eGQ<-$eO_wn{TfT`D}m^VS&s7 z9zsIbE;sz6)#H<=RkEYrI`-M^`rN@qN4v7e^A!7aNdR-Eow)PuCMk(-_5RUqDkJUG zERfENI1%BcaP^&~GSJf-N=+AL6gr+`3h$%`fiQ{tgw=O`DSH3#wynxsY^=a(_*i#) zLq?{kwkSOnN|bwET6JgjeBS#0wMTLC<}}JI#yU}xkYNkn9xtQ=g#Kdc^KetP)~E5w zny5GOc&vaaj$L#D^QPtl1NQ>Hylzu9I4;k&E6Ql*DZ~d_V}&m;gFoHx#=AwP^?=bB z5#>-QX~P#i_~)6JToNgPMk1dunym{rrVN!rQoOrJR9ZCJbQ7Axwx@?gLin(-9h1zs z?4Bvr*bOeC_XneR48a{(hDK?l%7@o>N*+9oK)*XpR91|6o&I!kY?w?~%euD=h@SG= zCBRw=m{1YL+xG7g5`^R1c$Jv2NEJdza3i8V)IOn{^vK3;PDn-&fVG549G}&!AxCLc zaUeU~?rKb&cgtO>AtGYRUf)ou6JaI zM&fwx)etKbM%y(Eo#^XB z_;I+Zw|?#K0aUBMW(SkAp*(kY09B;&y3}P=8RN7$raaTku#!lq;HyoxZExC7n+TeuFJ+du(wu)Ij7pA^^5g4J^}1{A}`ufIb)=TZLPSSk^K0LkO>ih9-1&P!BA zYGFlFS2v&`rr!2b)Yi{$BiulUnCctnuQ8GpIfKya05~{3CD(>a#{jz8YX)u87p}$u z&3D|A`{W@y$Si%cyUgJ>+!luvUu<+e}HQ?|di*u$9k8UCuZv*!|D;Z(d&&St2{8EbLg2a|11D<=2{_Z`QL*tLVXDT*Fm> z7qdB={b% zv8~Bd-nVu3zqYXR|B){11xoHD)ai;}(8-%OzMN+mv9AX+2K@dJDm4F_WYmVS{dmIp z>aFISeAnnU?p{d#?-!&kVw3_AbO3y|w+g}HRv*(4ycNCFD|C!=Ms|2*%TJchwISQj z#FwSx=N^&)xOIR6GF={TuqeFT#VDohf&4GVdR8ng>}L{ZDYczXFHv-vzHFYNr8R(C zpLL@ZV!>0AUUJ0dyn&w>62ayZ8CwyOmd?w4O7DPlDYqt(F}BP2*B+Ea;I!BpEe!vb z7OWQ5miZhqMa3ld(cDta3yDvQHQ=dN%tKbe(S!UG3uEdg7H{IPP`jS&kMXGP#8Y3I z!gNZXlK52H%8218$nsj>0QD$y-j9h@mLrni;Mo(QZ*Y1EGD~Io+-QvB)6aIOlUhLK^c~L3B3pQ0R3A#^0b2^X0DZBYWhY^$czxTqNyaQj zmULIkvaVg^@{oxtsk}=rNJ>W?_rb+1jc)ZTaze~0nXsuXq-Gwk)}>jKVx5lbU9??2 zj8dxR&OOS_jV>fEIR!szt*v%*A1l9yW{hAl%nK3A({`5#7OBb}1Bk@d{V)hWpkR8+3VPpO{mV!1Xg2Yd@mtb3Ab9;;&4&i<6le3^6m#7t+dKAc z?NaT8)`UQeAch1DB+>DxeAY(_xO#vAb1(^BpYLOR06?QAq`k$9QBOqJ%Xr!rXfgc^ zX%2Q$+Bh$GO*Yy#lk>W(m{Qhq)sweAujD7tC|CL?`#Z{uz*^H)EdQf8UfTqE{;37Cc(PE)?z9S!iufT9h{^R`0I?@+I05K{_ z3jxzUyH9n&;z~hEnC)>^|>2clp(H_)^>0P5dR(A(C`rTJ3vCI zKp-6=Vc##Pwj$B%mF{s<nKtI;j#{{#(xN1VN04c`j z7YM8RYsO8g1M=)WX0d|@h!sFe9X*ly106$OGrc%d`?TW+p3{O?GPQ~*tZGSWCslt5 z$Y!iu;WB1V^r!n0Xyt7CMe+v2q)Q}*1kac~NEpFvx^fyNPj_p?v0Yb@HjM5#7!`7g z=oDAKVK%%?J8#CDLw`XUD)Jo^MBb*?{7SMgpiD;ZJhN0ISygV0i-zlb?e-36gv45y z%Q@Xk7%Q#zntH-gM_+p&em%UNZC1UcGAx&L5zDvH@u#JryW#OtjX%pZL~)@g#AfRa zVY`Wiy@?CcX|wEI^Q~451G7jE1h4m8fS7Fvrkx5zL6v1|dJ5K{M(>QhAi{w;PWmpL z6=IV>%#o;YGVEpX!N3Wix240YB`ZpXtCZ=x|t)-Kf7<`y^-6tN`%0?ww`o5m$D+tAZLNm6wo-vY+g|KRkgCJAA6d`GEQcbxv0U{JnyuJm9nW>2^+Uj zmQQn)T6KgDK%n*95)A`*H3-k1<~=B3%Kzl-sucfzVAse>L1*Hd>M+Kx0drT!1c&#fZBP*Kx&Z7^l}y}`>5CI zB_XgT4OLB5Y?CltgvfJK02LA$O9=0^N<2LlKo>o!p`pzMjf)~hA*PM&LKgsB!epzq z=f0t~Yc|JIi52EVFgz$?VI?!d-ny6V{>8|RPgQA` zTyuJgF+C1i*Ap-6UiLn0S=o}*G6)H*`G!#44s=c~)u)-Nb41EZlAj}~zcrD*5Gs%m z{*a@bCcA6PX6ht9aqf^=ljpKTr+ziBWo0+7TId53Ic8wW55W%BTZUa8Ap`$MXg3sU7YqsWv6 z#}DN9lCM2ZufQ0;%$Kv?Q7A(Hvcv|pKj~Rz`AON$SNNR>1T!=d@c=1hma7!48GU`| zvWO1b;?Wxnd9_3&BBm?~`=#GW_1F4#lqtF?YlXW3}kFcoh~i0PMB0edbnlQbg8c*9DGc_nzRMZbMF?%tdw)yxs^{7ysHHH`u#nglpYV$DLQw2qyx~7sA z$VQ>_{imr_!qrtKVv;~uS^Wj#0JS|8lq0OK;t|8o0w+1e#sKR@otx&r8`mz-x6d)h5(Zgw zaeO3L>uTAo>TA#z7Qs1PW|F4`)e)kMxI2#fOpgn>L9UN7iKXvMwtRoM+3dRKK~<(=Ztk{kMy9bYB0xx6Faa` z#EmZhMquo;(-MiNBgyJhXShB!sOW{s$*}iaAW`)FydJ5~G}j%s{fC zD}`Qig1JV@4(ptAQiL5K+A(o_9jdv)S|vMv#$fe37SSi~)vzzJ)uo{i^ffA1K)M(j z5`^GH(Uzpi*>hO!{GyBy$ji|EgP_gX-JRaBmhd+;#-Wr#Q~{wkU}DF8tn9gUa^v>M z{lk3dh4>NWV#$WtvBU9txw+ZwfvV$%;vsF&Y@s(OX+kQiLwb`%w^Bt==yud7!kdZ)sk~uVbr654_SsR zJHopQ@y+7Nr@M;Z;CvKyObd6E$SPThn!cZ)!FD*4kSByC)e~g+hbs=8<#CUSZ_9Vu zyu=WbD+)p^ap+o@l~DCt^kAg4R|#|8pL^&d!?I5y*iQ80+@{;jge;}nN#PLQFxsKZ zN=`iT8~Yq=2UTz^2l=G^j?f8Q5}!id5#rO-U&V;DQun81==8Ry)`bP&B)5xJ2`qD& zh_~{DpqP)cf4D(x`Kj0kFee>0Wn9YGiv&Ay0FwgCq{SP=h}(_AJCH*|Ld~NHjEIz1 zlOu&N4N0->b#!4@JY8TNXLxVy365rQZv?+T+cQ|?_&oqvkSct`vh_h#U z`G98tmjLn7xzuD3F;Ex{BdR(s>NGCCNeqfd%oDm=j80>)6Sk@;Xr8OK>k{Vr4nPT> zw85rqMi|@X)Zh&4)lwL$c71TT-&I)tINIJLFH@+HgDUB*$jARJNM|M0r*>*ve{VbT zGs845J9S^yks}Gv&v!s)bXtC?IId%5m0vcO5JY>CIBmRUy04=^6G$&{MZBST?HbiD zIGLaob2749P(331uCp6Oh7GVR6GbAxYHV{Zt{B;}oLFLsZSc(7A+8pJU;e0z$cT- zKjV6!K4SU=3?scTjB2(+m_G7U3D?=vEdrRc)y)JiH24nVOA9YTD?Q_aiP2hnq)t#q zXszpFS(Y+M!_?CNmPgV<`IkL7gpSaFLmHJ9G7B$7`xE4RT5t5?SDP@1IZr=7RC~{ zm9*vafF=$cE#@vLiLyK6PURfmC7&btLOhLrSO?oKX8`tmMo%yO{@j4Jh8%*A{ z_GX1XUegdZS&x1*soYT`q@v*X@HjOO1mk-A^mLVl9Nt^P_k!BFzat z&-3*ys+Lq{poLs3ry_qvfM)$OBit0B`Yb8P>`Ynob&`sBaFbg1+QQmUy#mj+x`s_q z=agCM9eMAXaXRUA`6L;}fTo;*s;QBRU#A|1k6(%B=QHe9d?Z9LDs70Nso4_6Y(d+;sxhQcDc3bsgG-|?i1-#_k zeX-Se(JL5UFQREwTj2nDNRjKR3wfnv^yQaZ5*6Z(AA-p3&gwl!E}vlI72 zC$x?<4{mx|284I9BNCiHy}Ic3aKr2$E8SgzarxCG8WpPO@%(Rq&=pD)vDM*yDIzOP z+0!sUXm-n3guiG>$piF-XK-lbV^$<`5POH|LT|_dI*<#xMRMb@nfn#nzs`lEqa@&0 z7=Xp(Q8K7!2FN8Vj+ju^4+tUM3eB=44hD!9*TVG_m$N8F)8_iH`5Dyd?~w)PswlWh zDS{YBP=F{sf7clY8+9evzvca_D*Sg@_&>Sw$1i05t13KmiP&?s^IuhAL|cCsc!)RM zMwd+Y7RYICls!1Z;sEKIVF7g@o@z?`F!>|uMzZfun~2BtC$CTa6x;<~8VQ?pBAGP+ zWdPPhOhhs~wKus{(j=0VW7rRyO`p#_jS}P>=50J|y=6F-8mVCK0L`7?tgoP2PCMP% z(5=Z+z_sN2uPi7{{Llko>$7LAbyiJG8=BOg4I8ck_=M~7&d0JZ`)DW8jM1}cJG=rX zd{Zs$wll%;futmW4&rKTQvfy4p6MXD18|*M*QT7@d)J>W*3R%!IiR?43}{6oc&y(t zWWHX-EN%obEuV=@X>8IX`)4Bfy_THV0Oi$4FHzo}@OE;bamf03?)VwuV+&T=T2i8k zahE{f^eIM4OXHvUd0;&a@>390^-rQ08Izs`5}paRg9kyG7cw{yz;m;S?q^(XlHr^Q zzaG>4?+KOEb^Eq_Z0|)L>e^ z10JBLeHSzJLV=tfJ|I9DjNO(xi-(@TAkfJ-tgng<=r{?f%0_T;Nia%^9xq;)FrKAF zf>IO+5JmF1mDGjl4~6{L>fxCT^~kPFXJ#3RduL8|t5xJkqN)|5|J;2FYc=^L{0sW4 z(fxX0f9xYy3(8suLeM*aPHu34VppW`muIz`#h9-fJv0;!>9PK^NsgA$Cnnj^=_fi8 zaILT4;6tIsJngzZYJIywS;|>${Zp?|{C~Kd>IR~Y1&W>1Gc(Y+D=CAOwAu z=BRC+$6p`p(J^v2Ax+nU)Wr>ovwT@jhRP4gEXk`{#ZWPyZXb;J`qGzh@a)BQKB!>) zwk<}vSJeH z!rxs#3q2og^!io$5D4E(4%Ka{_d7#AXS_Arv*?L()*xg?-S9I;5fpxr>-hdSmfx!B zcNMCtYj$std+Fe_?hLCrvU~e<;|%~XK|I;p*fLwT8y){*6U9f=mu|1&_Gu=gtmT7l zrC~TM=H@OqG*;F1rs~Y0g05H{zURabet>D%?4(QF13~fYjxTKg?!20oqr={QYZ0M@ zQR6-2YvRn_n;MPnDxfe^!Pk_&`z(#Tk8?KVStt5q1SyTL&b{@Fl`ttg4~6Do>T{uK zrj)<)R$I`LZBJg5Rwv`HxyMP~fh6PyKUf!F-Z;2u%O2Vc>2{vOeYsz%0oG;v3|2Sq zfZmUi5*MyEisHl7CRkdu*r>lnY!#5TEDT{HHc<&g?^90&%9l$FUz69H9)C6eh`@w^Snv&Y_xKiHBo`mjZ*IgmhM6+rkQy1Cha3(TsUh z*dpN|FF?hNR1_%(1G`8Ubw+}b(Q#f)V?We50V;75)pE0uv7MPJzeZ44RC-Pg!~?)BI)MTC+5#G zRHbTw)n=9sqVMrT)k0cu-TWpr7{*wH4ccpIbo<|m8DaqwCH^MEk7{7PL>*`0WnXYd zO_nk?t+eP|iQ{EN0r&!!S~vA;Z)M*`bXPqjygl*zbQvb?P0XFXlAFWnp37Tt@0Qqi z9A#Uxb`mVYyu#J8a1De?PVC_1$IXzR?P+5+Vv)yywBjkNZR;&G`OZNzx9qU);m7MJ ze#2O1^uuC-r18Yy&G%wgrV2jC)~}!U(BF5VD1hQ4uxGm*bLBJu2UG~Iu%e~9Gla*T zKhW=uv4cd)-wjuinSe@H)+_?=2J|D>2#*bf8~cK5{1xKN8N4K00wtBp;PJUi?WpN z$TLMPHa#`$2NEOSeX6|)YglHTt)kGb%Mchu+yOd^kJ>)&b`&46Og(=nDENJ`=XxA$TI7NRPiyT;_(meO^)>1l4yVQ+As)T<`>-&7yAFlvb zEcH0?$UX>*FMs5xhtQ)Ppf91}gWUQ={22h9P2&};Pb<7LVYe#|MQ^IUc3yE$T2rx z^J{m99}Xz0Hr0^i_}X_oSyN_bdIlM=zHApgLH7g(K(a)?e_6563XhF){t_SXnfb#0 z8WW*Vdl(yF=YE%E7Wmtadkby3x{7I#pp?}w{e2yxqRyepYBU9iZkOE?Ud*EWnK8eu z*Qs-_K{nZ8?`O{ym+QQY^_%p!Xjj%r$6-e=DHf9jC7-Ihv|c`i%8fp4!<$f^6SpXw z38$z9k)AG-NitxA7X;|bZ@Am?PjsT^z}a%f9Xk19VI--QLDW{7dXd~>u@K*j(hR<= zz8U`W)DNy6Ud&Bk z)NjrN9OC5!%QMxWh}m$PjV^;DXVgIz86eTnMo2)!ZUl=xk0Z?EiE&00kC8%B2r4ds zxLvoYC$ASDtxs@{iw>QyhTcSovPVaN1VpSOm|-kz^`+avNTwJAM~JPHz(6#4VLX(J zba8vs&&MyX`!bSHvqwAh4`r!nRAHdHiYD3p}KIimVu+(fFP-^^X|;fo1}JCGZzy_n$p z=96)N9HXM?2_#aSsdw)K>CX#sefepiP12^6l!2n1?rK(f;4t=C<@C~|U>2gUJ<&~# z3=86RrIU`l*>9&VBx{B@y%Utsh;eVxgr^Jox*UT~r2m_J?cp&;lKS?m27C5w-Ys3~ zJ>rjD%0*WFIlZkWR@rBAt>@Ie+o>;^#|$_hJGdJ2tAu$|#`*P{mRM?+>|cnhuo8gS z+Cnifja_kxv6{m3fuIN@psMStfHe3-(RLI<7T*CB$Cwl|raIpA&Noe}@-6d)qj+%D ziw~Qm>o0VQ%6xCnW9t^6-391!(CuzL$%>oTSbR1`5W%fcF)}t9Q-NIQmW%%@6b0-0 zlR)iu-P?1X!qkM*pu3g40v`m9>a02d87fsEtj`k`I!U@m72ZsFCT-w=83W3Es1|upWhbAN zfw)(4@>_$1Cr}4~UVUUckh|azM6jhyLBDqh6j88aHCS&FZP(Ev#D#W&sw7q769t47 z9f%ObB}v%4lm&Ej00s}KJQl=yQXfPx^=+HRIS1l%jMOkxq38NwH&KJP+gxqHqM(bQ zc#aql&PEE(bq`tT{*PEd!eYBuI`N5W8lzA+GB{ZiTKN9}ZYWrE5dMC0jwq)?%i)G0 z5D;CyIDGYy!a<75^q=djDs0o~VaTIMykvx)pqz;|>T%W!AqXO3w(k+ti-AB9?org$ zxpEzXV1R4)51rr`fG5ya6UThw6kqi=A!qmuj2y?WSkvf z^=JSf@qA~`^Jc>Iwy#;d5+e5N7qTG`IszfiP_OO3{$5~sehPm+(;S!5QVLUKg| zZyUt{X9%cUq1XUUUhrxN9d5)v!-6M_nrmpJ1w}*$VZQ1JhZk)L04FPExiIC-s%_V` ztFqU}HE@InZKbxX+9lC4LG^%@C)&PlatA~lG&TzdPR!@(g>3l>4IMSZcO0!hC%hF% zfL_L)^~vKn4j|Nc=ze#sZwL}6W%gh7no^qEN5XOc0Jz1Ad;sH)c;_ae6iqyFGycXJ z1R9kOc{4aALnaxlQ?HCi!AN)KyfL&1INDrw6{?D0pI`ZuXjBCghpXoa&vXZT%EUTz`d9hHW8#kJJ^bal*i^APFpwO=Nbzu*q(R_y z&#mA~j_)t)kS#()hclagHI7E4bR#sE^|>s_-(U^3$$s#AHApX|>+1k??gATayB>YG z1EB~2u=eQu!H+yIk~UzKuZ64IU@9KAyZCGlt^-=kjhN_^!+>includeEnd('debug'); var geometryUpdater = this._geometryUpdater; - var onTerrain = geometryUpdater._onTerrain; + // Other materials require additional batch table attributes that aren't updated at this time. + var onTerrain = geometryUpdater._onTerrain && geometryUpdater._materialProperty instanceof ColorMaterialProperty; var primitives = this._primitives; var groundPrimitives = this._groundPrimitives; From 5300fdaeb078fac924fe6da1a4aa416133df608d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 4 May 2018 16:02:37 -0400 Subject: [PATCH 29/40] fix headers in new development Sandcastle examples --- .../gallery/development/Ground Primitive Materials.html | 4 ++-- .../gallery/development/Terrain Entity Batching.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html index c23c3fcbdeb..4ebbdad5a59 100644 --- a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html +++ b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html @@ -4,8 +4,8 @@ - - + + Cesium Demo diff --git a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html index 1be7b71c811..9aaa92bc6a0 100644 --- a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html +++ b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html @@ -4,8 +4,8 @@ - - + + Cesium Demo From 892b7dbf63abb467823c520b96a3846cf88656bf Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 7 May 2018 17:40:58 -0400 Subject: [PATCH 30/40] add support for materials on dynamic terrain entities, fix texture coordinate rotation support --- CHANGES.md | 2 +- Source/DataSources/DynamicGeometryUpdater.js | 40 ++--- Source/Scene/GroundPrimitive.js | 35 +++- Source/Scene/ShadowVolumeAppearance.js | 153 ++++++++++-------- .../Builtin/Functions/lineDistance.glsl | 14 ++ Source/Shaders/ShadowVolumeAppearanceFS.glsl | 21 +-- Source/Shaders/ShadowVolumeAppearanceVS.glsl | 12 +- Specs/Renderer/BuiltinFunctionsSpec.js | 16 ++ Specs/Scene/ShadowVolumeAppearanceSpec.js | 50 +++--- 9 files changed, 220 insertions(+), 123 deletions(-) create mode 100644 Source/Shaders/Builtin/Functions/lineDistance.glsl diff --git a/CHANGES.md b/CHANGES.md index 431c2c90012..690976f74be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ Change Log * Removed `Scene.copyGlobeDepth`. Globe depth will now be copied by default when supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ##### Additions :tada: -* Added support for materials on static terrain entities and `GroundPrimitives`. This functionality requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so it is not available in Internet Explorer. Textured materials on static terrain entities and `GroundPrimitives` are best suited for notational patterns and are not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) +* Added support for materials on terrain entities and `GroundPrimitives`. This functionality requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so it is not available in Internet Explorer. Textured materials on terrain entities and `GroundPrimitives` are best suited for notational patterns and are not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) * Added `GroundPrimitive.supportsMaterials` and `Entity.supportsMaterialsforEntitiesOnTerrain`, both of which can be used to check if materials on terrain entities and `GroundPrimitives` is supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ### 1.45 - 2018-05-01 diff --git a/Source/DataSources/DynamicGeometryUpdater.js b/Source/DataSources/DynamicGeometryUpdater.js index bb6c72d6085..163eaaab442 100644 --- a/Source/DataSources/DynamicGeometryUpdater.js +++ b/Source/DataSources/DynamicGeometryUpdater.js @@ -77,8 +77,7 @@ define([ //>>includeEnd('debug'); var geometryUpdater = this._geometryUpdater; - // Other materials require additional batch table attributes that aren't updated at this time. - var onTerrain = geometryUpdater._onTerrain && geometryUpdater._materialProperty instanceof ColorMaterialProperty; + var onTerrain = geometryUpdater._onTerrain; var primitives = this._primitives; var groundPrimitives = this._groundPrimitives; @@ -101,32 +100,33 @@ define([ var shadows = this._geometryUpdater.shadowsProperty.getValue(time); var options = this._options; if (!defined(geometry.fill) || geometry.fill.getValue(time)) { + var fillMaterialProperty = geometryUpdater.fillMaterialProperty; + var isColorAppearance = fillMaterialProperty instanceof ColorMaterialProperty; + var appearance; + var closed = geometryUpdater._getIsClosed(options); + if (isColorAppearance) { + appearance = new PerInstanceColorAppearance({ + closed: closed + }); + } else { + var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); + this._material = material; + appearance = new MaterialAppearance({ + material : material, + translucent : material.isTranslucent(), + closed : closed + }); + } + if (onTerrain) { options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT; this._primitive = groundPrimitives.add(new GroundPrimitive({ geometryInstances : this._geometryUpdater.createFillGeometryInstance(time), + appearance : appearance, asynchronous : false, shadows : shadows })); } else { - var fillMaterialProperty = geometryUpdater.fillMaterialProperty; - var isColorAppearance = fillMaterialProperty instanceof ColorMaterialProperty; - var appearance; - var closed = geometryUpdater._getIsClosed(options); - if (isColorAppearance) { - appearance = new PerInstanceColorAppearance({ - closed: closed - }); - } else { - var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material); - this._material = material; - appearance = new MaterialAppearance({ - material : material, - translucent : material.isTranslucent(), - closed : closed - }); - } - options.vertexFormat = appearance.vertexFormat; var fillInstance = this._geometryUpdater.createFillGeometryInstance(time); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 8b04f8f91d6..01044494b12 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -11,12 +11,14 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', + '../Core/EllipseGeometry', '../Core/GeographicTilingScheme', '../Core/GeometryInstance', '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', '../Core/Rectangle', + '../Core/RectangleGeometry', '../Core/Resource', '../Renderer/DrawCommand', '../Renderer/Pass', @@ -39,12 +41,14 @@ define([ defineProperties, destroyObject, DeveloperError, + EllipseGeometry, GeographicTilingScheme, GeometryInstance, isArray, CesiumMath, OrientedBoundingBox, Rectangle, + RectangleGeometry, Resource, DrawCommand, Pass, @@ -473,6 +477,26 @@ define([ return rectangle; } + function getUnrotatedRectangle(ellipsoid, geometry, boundingRectangle, result) { + if (geometry instanceof RectangleGeometry) { + Rectangle.clone(geometry._rectangle, result); + } else if (geometry instanceof EllipseGeometry) { + var rotation = geometry._rotation; + geometry._rotation = 0.0; + Rectangle.clone(geometry.rectangle, result); + geometry._rotation = rotation; + } else { + Rectangle.clone(boundingRectangle, result); + } + } + + function getGeometryRotation(geometry) { + if ((geometry instanceof RectangleGeometry) || (geometry instanceof EllipseGeometry)) { + return geometry._rotation; + } + return 0.0; + } + var scratchDiagonalCartesianNE = new Cartesian3(); var scratchDiagonalCartesianSW = new Cartesian3(); var scratchDiagonalCartographic = new Cartographic(); @@ -718,6 +742,7 @@ define([ return GroundPrimitive._initPromise; }; + var geometryRectangleScratch = new Rectangle(); /** * Called when {@link Viewer} or {@link CesiumWidget} render the scene to * get the draw commands needed to render this primitive. @@ -824,13 +849,17 @@ define([ geometry = instance.geometry; instanceType = geometry.constructor; - rectangle = getRectangle(frameState, geometry); + var boundingRectangle = getRectangle(frameState, geometry); + var geometryRectangle = geometryRectangleScratch; + getUnrotatedRectangle(ellipsoid, geometry, boundingRectangle, geometryRectangle); + var geometryRotation = getGeometryRotation(geometry); + var texcoordRotation = geometry._stRotation; if (!allSameColor) { if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(rectangle, ellipsoid, frameState.mapProjection, this._maxHeight, geometry._stRotation); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, geometryRectangle, ellipsoid, frameState.mapProjection, this._maxHeight, texcoordRotation, geometryRotation); } else { - attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(rectangle, ellipsoid, frameState.mapProjection, geometry._stRotation); + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, geometryRectangle, ellipsoid, frameState.mapProjection, texcoordRotation, geometryRotation); } } else { attributes = {}; diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 1493104525c..7d997dc2dc0 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -328,45 +328,40 @@ define([ } }); - var cartographicScratch = new Cartographic(); - var rectangleCenterScratch = new Cartographic(); - var northCenterScratch = new Cartesian3(); - var southCenterScratch = new Cartesian3(); - var eastCenterScratch = new Cartesian3(); - var westCenterScratch = new Cartesian3(); + 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()]; + var rotatedPoint2DScratch = new Cartesian2(); var rotation2DScratch = new Matrix2(); var min2DScratch = new Cartesian2(); var max2DScratch = new Cartesian2(); - function getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) { - var theta = defaultValue(textureCoordinateRotation, 0.0); - - // Compute approximate scale such that the rectangle, if scaled and rotated, - // will completely enclose the unrotated/unscaled rectangle. - var cosTheta = Math.cos(theta); - var sinTheta = Math.sin(theta); - - // Build a rectangle centered in 2D space approximating the input rectangle's dimensions - var cartoCenter = Rectangle.center(rectangle, rectangleCenterScratch); - - var carto = cartographicScratch; - carto.latitude = cartoCenter.latitude; + var rectangleCenterScratch = new Cartographic(); - carto.longitude = rectangle.west; - var westCenter = Cartographic.toCartesian(carto, ellipsoid, westCenterScratch); + // boundingRectangle is a rectangle around the rotated geometry while geometryRectangle is the bounding box around the unrotated geometry. + function addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation) { + textureCoordinateRotation = defaultValue(textureCoordinateRotation, 0.0); + geometryRotation = defaultValue(geometryRotation, 0.0); - carto.longitude = rectangle.east; - var eastCenter = Cartographic.toCartesian(carto, ellipsoid, eastCenterScratch); + var i; + var point2D; - carto.longitude = cartoCenter.longitude; - carto.latitude = rectangle.north; - var northCenter = Cartographic.toCartesian(carto, ellipsoid, northCenterScratch); + // Assume a computed "east-north" texture coordinate system. + // The "desired" texture coordinate system forms a rectangle around the geometryRectangle that completely bounds it. + // Both the geometry and the "desired" texture coordinate system rectangles may be rotated. + // Compute the corners of the "desired" texture coordinate system in "east-north" texture space by the following: + // - Treat all rectangles as if the centers are at 0, 0 + // - rotate the geometryRectangle by geometryRotation - textureCoordinateRotation + // - compute corners of box bounding transformed geometryRectangle. Call this "desired local" + // - rotate 3 of the corners in "desired local" by textureCoordinateRotation, then compute the equivalent of + // each point in the "east-north" texture coordinate system. - carto.latitude = rectangle.south; - var southCenter = Cartographic.toCartesian(carto, ellipsoid, southCenterScratch); + // Perform all computations in cartographic coordinates, no need to project. - var northSouthHalfDistance = Cartesian3.distance(northCenter, southCenter) * 0.5; - var eastWestHalfDistance = Cartesian3.distance(eastCenter, westCenter) * 0.5; + var northSouthHalfDistance = geometryRectangle.height * 0.5; + var eastWestHalfDistance = geometryRectangle.width * 0.5; + var boundingRectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); var points2D = points2DScratch; points2D[0].x = eastWestHalfDistance; @@ -381,7 +376,6 @@ define([ points2D[3].x = -eastWestHalfDistance; points2D[3].y = -northSouthHalfDistance; - // Rotate the dimensions rectangle and compute min/max in rotated space var min2D = min2DScratch; min2D.x = Number.POSITIVE_INFINITY; min2D.y = Number.POSITIVE_INFINITY; @@ -389,33 +383,59 @@ define([ max2D.x = Number.NEGATIVE_INFINITY; max2D.y = Number.NEGATIVE_INFINITY; - var rotation2D = Matrix2.fromRotation(-theta, rotation2DScratch); - for (var i = 0; i < 4; ++i) { - var point2D = points2D[i]; - Matrix2.multiplyByVector(rotation2D, point2D, point2D); + var toDesiredLocal = Matrix2.fromRotation(geometryRotation - textureCoordinateRotation, rotation2DScratch); + for (i = 0; i < 4; ++i) { + point2D = Matrix2.multiplyByVector(toDesiredLocal, points2D[i], rotatedPoint2DScratch); Cartesian2.minimumByComponent(point2D, min2D, min2D); Cartesian2.maximumByComponent(point2D, max2D, max2D); } - // Depending on the rotation, east/west may be more appropriate for vertical scale than horizontal - var scaleU = 1.0; - var scaleV = 1.0; - if (Math.abs(sinTheta) < Math.abs(cosTheta)) { - scaleU = eastWestHalfDistance / ((max2D.x - min2D.x) * 0.5); - scaleV = northSouthHalfDistance / ((max2D.y - min2D.y) * 0.5); - } else { - scaleU = eastWestHalfDistance / ((max2D.y - min2D.y) * 0.5); - scaleV = northSouthHalfDistance / ((max2D.x - min2D.x) * 0.5); + points2D[0].x = min2D.x; + points2D[0].y = min2D.y; + + points2D[1].x = min2D.x; + points2D[1].y = max2D.y; + + points2D[2].x = max2D.x; + points2D[2].y = min2D.y; + + var toDesiredInComputed = Matrix2.fromRotation(textureCoordinateRotation, rotation2DScratch); + northSouthHalfDistance = geometryRectangle.height * 0.5; + eastWestHalfDistance = geometryRectangle.width * 0.5; + + for (i = 0; i < 3; ++i) { + point2D = points2D[i]; + Matrix2.multiplyByVector(toDesiredInComputed, point2D, point2D); + // Convert point into east-north texture coordinate space + point2D.x = (point2D.x + boundingRectangleCenter.longitude - boundingRectangle.west) / boundingRectangle.width; + point2D.y = (point2D.y + boundingRectangleCenter.latitude - boundingRectangle.south) / boundingRectangle.height; } - return new GeometryInstanceAttribute({ + // Encode these points in the batch table. + // These will be used to create lines in east-north texture coordinate space. + // Distance from an east-north texture coordinate to each line is a desired texture coordinate. + var minXYCorner = points2D[0]; + var maxYCorner = points2D[1]; + var maxXCorner = 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 : [sinTheta, cosTheta, scaleU, scaleV] // Precompute trigonometry for rotation and inverse of scale + value : [minXYCorner.x, minXYCorner.y, inverseExtentX, inverseExtentY] }); } + var cartographicScratch = new Cartographic(); var cornerScratch = new Cartesian3(); var northWestScratch = new Cartesian3(); var southEastScratch = new Cartesian3(); @@ -585,16 +605,19 @@ define([ * @see ShadowVolumeAppearance * @private * - * @param {Rectangle} rectangle Rectangle object that the points will approximately bound + * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound + * @param {Rectangle} geometryRectangle If the geometry can be rotated, the bounding rectangle for the unrotated geometry. * @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. * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @param {Number} [geometryRotation] If the geometry can be rotated, the rotation in radians. * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(rectangle, ellipsoid, projection, height, textureCoordinateRotation) { + ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(boundingRectangle, geometryRectangle, ellipsoid, projection, height, textureCoordinateRotation, geometryRotation) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('boundingRectangle', boundingRectangle); + Check.typeOf.object('geometryRectangle', geometryRectangle); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); @@ -602,11 +625,10 @@ define([ var corner = cornerScratch; var eastward = eastwardScratch; var northward = northwardScratch; - computeRectangleBounds(rectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); + computeRectangleBounds(boundingRectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); - var attributes = { - stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) - }; + var attributes = {}; + addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation); var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch); attributes.southWest_HIGH = new GeometryInstanceAttribute({ @@ -634,7 +656,7 @@ define([ value : Cartesian3.pack(northward, [0, 0, 0]) }); - add2DTextureCoordinateAttributes(rectangle, projection, attributes); + add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); return attributes; }; @@ -674,27 +696,30 @@ define([ * @see ShadowVolumeAppearance * @private * - * @param {Rectangle} rectangle Rectangle object that the spherical extents will approximately bound + * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound + * @param {Rectangle} geometryRectangle If the geometry can be rotated, the bounding rectangle for the unrotated geometry. * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation + * @param {Number} [geometryRotation] If the geometry can be rotated, the rotation in radians. * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. */ - ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(rectangle, ellipsoid, projection, textureCoordinateRotation) { + ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, geometryRectangle, ellipsoid, projection, textureCoordinateRotation, geometryRotation) { //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('rectangle', rectangle); + Check.typeOf.object('boundingRectangle', boundingRectangle); + Check.typeOf.object('geometryRectangle', geometryRectangle); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); // rectangle cartographic coords !== spherical because it's on an ellipsoid - var southWestExtents = latLongToSpherical(rectangle.south, rectangle.west, ellipsoid, sphericalScratch); + var southWestExtents = latLongToSpherical(boundingRectangle.south, boundingRectangle.west, ellipsoid, sphericalScratch); // Slightly pad extents to avoid floating point error when fragment culling at edges. var south = southWestExtents.x - CesiumMath.EPSILON5; var west = southWestExtents.y - CesiumMath.EPSILON5; - var northEastExtents = latLongToSpherical(rectangle.north, rectangle.east, ellipsoid, sphericalScratch); + var northEastExtents = latLongToSpherical(boundingRectangle.north, boundingRectangle.east, ellipsoid, sphericalScratch); var north = northEastExtents.x + CesiumMath.EPSILON5; var east = northEastExtents.y + CesiumMath.EPSILON5; @@ -707,11 +732,11 @@ define([ componentsPerAttribute: 4, normalize: false, value : [south, west, latitudeRangeInverse, longitudeRangeInverse] - }), - stSineCosineUVScale : getTextureCoordinateRotationAttribute(rectangle, ellipsoid, textureCoordinateRotation) + }) }; - add2DTextureCoordinateAttributes(rectangle, projection, attributes); + addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation); + add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); return attributes; }; @@ -719,13 +744,13 @@ define([ 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.stSineCosineUVScale); + defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); }; ShadowVolumeAppearance.hasAttributesForSphericalExtents = function(attributes) { return defined(attributes.sphericalExtents) && defined(attributes.planes2D_HIGH) && defined(attributes.planes2D_LOW) && - defined(attributes.stSineCosineUVScale); + defined(attributes.uMaxVmax) && defined(attributes.uvMinAndExtents); }; function shouldUseSpherical(rectangle) { diff --git a/Source/Shaders/Builtin/Functions/lineDistance.glsl b/Source/Shaders/Builtin/Functions/lineDistance.glsl new file mode 100644 index 00000000000..4a239d93339 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/lineDistance.glsl @@ -0,0 +1,14 @@ +/** + * Computes distance from an point in 2D to a line in 2D. + * + * @name czm_lineDistance + * @glslFunction + * + * param {vec2} point1 A point along the line. + * param {vec2} point2 A point along the line. + * param {vec2} point A point that may or may not be on the line. + * returns {float} The distance from the point to the line. + */ +float czm_lineDistance(vec2 point1, vec2 point2, vec2 point) { + return abs((point2.y - point1.y) * point.x - (point2.x - point1.x) * point.y + point2.x * point1.y - point2.y * point1.x) / distance(point2, point1); +} diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index 3d285759fde..b501adfd8c3 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -10,7 +10,9 @@ varying vec2 v_inversePlaneExtents; varying vec4 v_westPlane; varying vec4 v_southPlane; #endif // SPHERICAL -varying vec4 v_stSineCosineUVScale; +varying vec2 v_uvMin; +varying vec3 v_uMaxAndInverseDistance; +varying vec3 v_vMaxAndInverseDistance; #endif // TEXTURE_COORDINATES #ifdef PER_INSTANCE_COLOR @@ -52,21 +54,22 @@ void main(void) #endif #ifdef TEXTURE_COORDINATES + vec2 uv; #ifdef SPHERICAL // Treat world coords as a sphere normal for spherical coordinates vec2 sphericalLatLong = czm_approximateSphericalCoordinates(worldCoordinate); - float u = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z; - float v = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w; + uv.x = (sphericalLatLong.y - v_sphericalExtents.y) * v_sphericalExtents.w; + uv.y = (sphericalLatLong.x - v_sphericalExtents.x) * v_sphericalExtents.z; #else // SPHERICAL // Unpack planes and transform to eye space - float u = czm_planeDistance(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.y; - float v = czm_planeDistance(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.x; + uv.x = czm_planeDistance(v_westPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.x; + uv.y = czm_planeDistance(v_southPlane, eyeCoordinate.xyz / eyeCoordinate.w) * v_inversePlaneExtents.y; #endif // SPHERICAL #endif // TEXTURE_COORDINATES #ifdef PICK #ifdef CULL_FRAGMENTS - if (0.0 <= u && u <= 1.0 && 0.0 <= v && v <= 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(); } @@ -76,7 +79,7 @@ void main(void) #else // PICK #ifdef CULL_FRAGMENTS - if (u <= 0.0 || 1.0 <= u || v <= 0.0 || 1.0 <= v) { + if (uv.x <= 0.0 || 1.0 <= uv.x || uv.y <= 0.0 || 1.0 <= uv.y) { discard; } #endif @@ -125,8 +128,8 @@ void main(void) #endif #ifdef USES_ST - materialInput.st.x = v_stSineCosineUVScale.y * (v - 0.5) * v_stSineCosineUVScale.z + v_stSineCosineUVScale.x * (u - 0.5) * v_stSineCosineUVScale.w + 0.5; - materialInput.st.y = v_stSineCosineUVScale.y * (u - 0.5) * v_stSineCosineUVScale.w - v_stSineCosineUVScale.x * (v - 0.5) * v_stSineCosineUVScale.z + 0.5; + materialInput.st.x = czm_lineDistance(v_uvMin, v_uMaxAndInverseDistance.xy, uv) * v_uMaxAndInverseDistance.z; + materialInput.st.y = czm_lineDistance(v_uvMin, v_vMaxAndInverseDistance.xy, uv) * v_vMaxAndInverseDistance.z; #endif czm_material material = czm_getMaterial(materialInput); diff --git a/Source/Shaders/ShadowVolumeAppearanceVS.glsl b/Source/Shaders/ShadowVolumeAppearanceVS.glsl index 74f4dfca525..d15f79cd25d 100644 --- a/Source/Shaders/ShadowVolumeAppearanceVS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceVS.glsl @@ -20,7 +20,9 @@ varying vec2 v_inversePlaneExtents; varying vec4 v_westPlane; varying vec4 v_southPlane; #endif // SPHERICAL -varying vec4 v_stSineCosineUVScale; +varying vec2 v_uvMin; +varying vec3 v_uMaxAndInverseDistance; +varying vec3 v_vMaxAndInverseDistance; #endif // TEXTURE_COORDINATES void main() @@ -38,7 +40,6 @@ void main() #ifdef TEXTURE_COORDINATES #ifdef SPHERICAL v_sphericalExtents = czm_batchTable_sphericalExtents(batchId); - v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); #else // SPHERICAL #ifdef COLUMBUS_VIEW_2D vec4 planes2D_high = czm_batchTable_planes2D_HIGH(batchId); @@ -65,7 +66,12 @@ void main() v_southPlane = vec4(northWard, -dot(northWard, southWestCorner)); v_inversePlaneExtents = vec2(1.0 / eastExtent, 1.0 / northExtent); #endif // SPHERICAL - v_stSineCosineUVScale = czm_batchTable_stSineCosineUVScale(batchId); + vec4 uvMinAndExtents = czm_batchTable_uvMinAndExtents(batchId); + vec4 uMaxVmax = czm_batchTable_uMaxVmax(batchId); + + v_uMaxAndInverseDistance = vec3(uMaxVmax.xy, uvMinAndExtents.z); + v_vMaxAndInverseDistance = vec3(uMaxVmax.zw, uvMinAndExtents.w); + v_uvMin = uvMinAndExtents.xy; #endif // TEXTURE_COORDINATES #ifdef PER_INSTANCE_COLOR diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index 911a327440a..b766adab0c8 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -157,6 +157,22 @@ defineSuite([ }).contextToRender(); }); + it('has czm_lineDistance', function() { + var fs = + 'void main() { ' + + ' vec2 point1 = vec2(0.0, 0.0); ' + + ' vec2 point2 = vec2(1.0, 0.0); ' + + ' vec2 point = vec2(0.5, 1.0); ' + + ' float expected = 1.0; ' + + ' float actual = czm_lineDistance(point1, point2, point); ' + + ' gl_FragColor = vec4(actual == expected); ' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + it('has czm_tangentToEyeSpaceMatrix', function() { var fs = 'void main() { ' + diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index b2f9d3be195..1df6d78e9e4 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -43,8 +43,8 @@ defineSuite([ 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, unitSphereEllipsoid, projection); - var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection); + var largeRectangleAttributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(largeTestRectangle, largeTestRectangle, unitSphereEllipsoid, projection); + var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, smallTestRectangle, unitSphereEllipsoid, projection); var perInstanceColorMaterialAppearance = new PerInstanceColorAppearance(); var flatPerInstanceColorMaterialAppearance = new PerInstanceColorAppearance({ @@ -169,27 +169,31 @@ defineSuite([ }); it('provides attributes for rotating texture coordinates', function() { - var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, 0.0, CesiumMath.PI_OVER_TWO); - - var stRotationAttribute = attributes.stSineCosineUVScale; - expect(stRotationAttribute.componentDatatype).toEqual(ComponentDatatype.FLOAT); - expect(stRotationAttribute.componentsPerAttribute).toEqual(4); - expect(stRotationAttribute.normalize).toEqual(false); - - var value = stRotationAttribute.value; - expect(value[0]).toEqualEpsilon(Math.sin(CesiumMath.PI_OVER_TWO), CesiumMath.EPSILON7); - expect(value[1]).toEqualEpsilon(Math.cos(CesiumMath.PI_OVER_TWO), CesiumMath.EPSILON7); - expect(value[2]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); // 90 degree rotation of a square, so no scale - expect(value[3]).toEqualEpsilon(1.0, CesiumMath.EPSILON7); - - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, unitSphereEllipsoid, projection, 0.0, CesiumMath.PI_OVER_FOUR); - value = attributes.stSineCosineUVScale.value; - expect(value[0]).toEqualEpsilon(Math.sin(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); - expect(value[1]).toEqualEpsilon(Math.cos(CesiumMath.PI_OVER_FOUR), CesiumMath.EPSILON7); - - var expectedScale = Math.sqrt(2.0) * 0.5; // 45 degree rotation of a square, so scale to square diagonal - expect(value[2]).toEqualEpsilon(expectedScale, CesiumMath.EPSILON7); - expect(value[3]).toEqualEpsilon(expectedScale, CesiumMath.EPSILON7); + var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(smallTestRectangle, smallTestRectangle, unitSphereEllipsoid, projection, 0.0, CesiumMath.PI_OVER_TWO); + + var uMaxVmax = attributes.uMaxVmax; + var uvMinAndExtents = attributes.uvMinAndExtents; + expect(uMaxVmax.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(uMaxVmax.componentsPerAttribute).toEqual(4); + expect(uMaxVmax.normalize).toEqual(false); + + expect(uvMinAndExtents.componentDatatype).toEqual(ComponentDatatype.FLOAT); + expect(uvMinAndExtents.componentsPerAttribute).toEqual(4); + expect(uvMinAndExtents.normalize).toEqual(false); + + // 90 degree rotation of a square, so "max" point in Y direction is 0,0, "max" point in X direction is 1,1 + var value = uMaxVmax.value; + expect(value[0]).toEqual(0.0); + expect(value[1]).toEqual(0.0); + expect(value[2]).toEqual(1.0); + expect(value[3]).toEqual(1.0); + + // So "min" of texture coordinates is at 1, 0 and extents are just 1s + value = uvMinAndExtents.value; + expect(value[0]).toEqual(1.0); + expect(value[1]).toEqual(0.0); + expect(value[2]).toEqual(1.0); + expect(value[3]).toEqual(1.0); }); it('checks for spherical extent attributes', function() { From ac71c04de31ed6c23bdbbbcaa634bbcf9b8f3e49 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 8 May 2018 22:55:06 -0400 Subject: [PATCH 31/40] compute tight rectangles for rotated texture coordinates [ci skip] --- Source/Core/CircleGeometry.js | 8 ++ Source/Core/CorridorGeometry.js | 8 ++ Source/Core/EllipseGeometry.js | 33 ++++++++ Source/Core/PolygonGeometry.js | 48 +++++++++++ Source/Core/RectangleGeometry.js | 31 ++++++++ Source/Scene/GroundPrimitive.js | 34 +++----- Source/Scene/ShadowVolumeAppearance.js | 105 ++++++++----------------- 7 files changed, 170 insertions(+), 97 deletions(-) diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js index cfc74b95eb5..677605a836e 100644 --- a/Source/Core/CircleGeometry.js +++ b/Source/Core/CircleGeometry.js @@ -184,6 +184,14 @@ define([ get : function() { return this._ellipseGeometry.rectangle; } + }, + /** + * @private + */ + unrotatedTextureRectangle : { + get : function() { + return this._ellipseGeometry.unrotatedTextureRectangle; + } } }); diff --git a/Source/Core/CorridorGeometry.js b/Source/Core/CorridorGeometry.js index ae6afca4dad..2f02a225777 100644 --- a/Source/Core/CorridorGeometry.js +++ b/Source/Core/CorridorGeometry.js @@ -1045,6 +1045,14 @@ define([ } return this._rectangle; } + }, + /** + * @private + */ + unrotatedTextureRectangle : { + get : function() { + return this.rectangle; + } } }); diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index 76eeab37945..697ba98cc62 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -675,6 +675,27 @@ define([ return rectangle; } + var scratchEllipseGeometry = new EllipseGeometry({ + center : Cartesian3.ZERO, + semiMajorAxis : 2, + semiMinorAxis : 1 + }); + function computeUnrotatedTextureRectangle(ellipseGeometry) { + var rotatedEllipse = scratchEllipseGeometry; + rotatedEllipse._center = Cartesian3.clone(ellipseGeometry._center, rotatedEllipse._center); + rotatedEllipse._semiMajorAxis = ellipseGeometry._semiMajorAxis; + rotatedEllipse._semiMinorAxis = ellipseGeometry._semiMinorAxis; + rotatedEllipse._ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, rotatedEllipse._ellipsoid); + rotatedEllipse._granularity = ellipseGeometry._granularity; + + // Rotate to align the texture coordinates with ENU + // Ellipse texture rotation is backwards, so + instead of - + rotatedEllipse._rotation = ellipseGeometry._rotation + ellipseGeometry._stRotation; + rotatedEllipse._rectangle = undefined; + + return computeRectangle(rotatedEllipse); + } + /** * A description of an ellipse on an ellipsoid. Ellipse geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}. * @@ -755,6 +776,7 @@ define([ this._workerName = 'createEllipseGeometry'; this._rectangle = undefined; + this._unrotatedTextureRectangle = undefined; } /** @@ -964,6 +986,17 @@ define([ } return this._rectangle; } + }, + /** + * @private + */ + unrotatedTextureRectangle : { + get : function() { + if (!defined(this._unrotatedTextureRectangle)) { + this._unrotatedTextureRectangle = computeUnrotatedTextureRectangle(this); + } + return this._unrotatedTextureRectangle; + } } }); diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 4045486b150..20d1882353e 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -18,6 +18,7 @@ define([ './GeometryPipeline', './IndexDatatype', './Math', + './Matrix2', './Matrix3', './PolygonGeometryLibrary', './PolygonPipeline', @@ -45,6 +46,7 @@ define([ GeometryPipeline, IndexDatatype, CesiumMath, + Matrix2, Matrix3, PolygonGeometryLibrary, PolygonPipeline, @@ -595,6 +597,7 @@ define([ this._workerName = 'createPolygonGeometry'; this._rectangle = undefined; + this._unrotatedTextureRectangle = undefined; /** * The number of elements used to pack the object into an array. @@ -907,6 +910,35 @@ define([ }); }; + var rectangleCenterScratch = new Cartographic(); + var cartographicScratch = new Cartographic(); + var rotationScratch = new Matrix2(); + var cartesian2Scratch = new Cartesian2(); + function computeRectangleRotatedPositions(originalRectangle, positions, ellipsoid, angle) { + var result = new Rectangle(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); + var center = Rectangle.center(originalRectangle, rectangleCenterScratch); + var rotation = Matrix2.fromRotation(angle, rotationScratch); + + var positionsLength = positions.length; + var cartesian2 = cartesian2Scratch; + for (var i = 0; i < positionsLength; i++) { + var cartographic = Cartographic.fromCartesian(positions[i], ellipsoid, cartographicScratch); + cartesian2.x = cartographic.longitude - center.longitude; + cartesian2.y = cartographic.latitude - center.latitude; + + Matrix2.multiplyByVector(rotation, cartesian2, cartesian2); + + cartesian2.x += center.longitude; + cartesian2.y += center.latitude; + + result.west = Math.min(result.west, cartesian2.x); + result.east = Math.max(result.east, cartesian2.x); + result.south = Math.min(result.south, cartesian2.y); + result.north = Math.max(result.north, cartesian2.y); + } + return result; + } + defineProperties(PolygonGeometry.prototype, { /** * @private @@ -924,6 +956,22 @@ define([ return this._rectangle; } + }, + /** + * @private + */ + unrotatedTextureRectangle : { + get : function() { + if (!defined(this._unrotatedTextureRectangle)) { + var positions = this._polygonHierarchy.positions; + if (!defined(positions) || positions.length < 3) { + this._unrotatedTextureRectangle = new Rectangle(); + } else { + this._unrotatedTextureRectangle = computeRectangleRotatedPositions(this.rectangle, positions, this._ellipsoid, -this._stRotation); + } + } + return this._unrotatedTextureRectangle; + } } }); diff --git a/Source/Core/RectangleGeometry.js b/Source/Core/RectangleGeometry.js index 26f1df8f561..8de4a0c1f7a 100644 --- a/Source/Core/RectangleGeometry.js +++ b/Source/Core/RectangleGeometry.js @@ -580,6 +580,24 @@ define([ return Rectangle.fromCartesianArray(positions, rectangleGeometry._ellipsoid); } + var scratchRectangleGeometry = new RectangleGeometry({ + rectangle : new Rectangle() + }); + function computeUnrotatedTextureRectangle(rectangleGeometry) { + var rotatedRectangle = scratchRectangleGeometry; + + rotatedRectangle._rectangle = Rectangle.clone(rectangleGeometry._rectangle, rotatedRectangle._rectangle); + rotatedRectangle._granularity = rectangleGeometry._granularity; + rotatedRectangle._ellipsoid = Ellipsoid.clone(rectangleGeometry._ellipsoid, rotatedRectangle._ellipsoid); + rotatedRectangle._surfaceHeight = rectangleGeometry._surfaceHeight; + + // Rotate to align the texture coordinates with ENU + rotatedRectangle._rotation = rectangleGeometry._rotation - rectangleGeometry._stRotation; + + var result = computeRectangle(rotatedRectangle); + return Rectangle.clone(result); + } + /** * A description of a cartographic rectangle on an ellipsoid centered at the origin. Rectangle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}. * @@ -651,6 +669,8 @@ define([ this._shadowVolume = defaultValue(options.shadowVolume, false); this._workerName = 'createRectangleGeometry'; this._rotatedRectangle = undefined; + + this._unrotatedTextureRectangle = undefined; } /** @@ -872,6 +892,17 @@ define([ } return this._rotatedRectangle; } + }, + /** + * @private + */ + unrotatedTextureRectangle : { + get : function() { + if (!defined(this._unrotatedTextureRectangle)) { + this._unrotatedTextureRectangle = computeUnrotatedTextureRectangle(this); + } + return this._unrotatedTextureRectangle; + } } }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 01044494b12..e4cd29293f4 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -17,6 +17,7 @@ define([ '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', + '../Core/PolygonGeometry', '../Core/Rectangle', '../Core/RectangleGeometry', '../Core/Resource', @@ -47,6 +48,7 @@ define([ isArray, CesiumMath, OrientedBoundingBox, + PolygonGeometry, Rectangle, RectangleGeometry, Resource, @@ -477,24 +479,11 @@ define([ return rectangle; } - function getUnrotatedRectangle(ellipsoid, geometry, boundingRectangle, result) { - if (geometry instanceof RectangleGeometry) { - Rectangle.clone(geometry._rectangle, result); - } else if (geometry instanceof EllipseGeometry) { - var rotation = geometry._rotation; - geometry._rotation = 0.0; - Rectangle.clone(geometry.rectangle, result); - geometry._rotation = rotation; - } else { - Rectangle.clone(boundingRectangle, result); - } - } - - function getGeometryRotation(geometry) { - if ((geometry instanceof RectangleGeometry) || (geometry instanceof EllipseGeometry)) { - return geometry._rotation; + function getStRotation(geometry) { + if ((geometry instanceof PolygonGeometry) || (geometry instanceof EllipseGeometry)) { + return -geometry._stRotation; } - return 0.0; + return geometry._stRotation; } var scratchDiagonalCartesianNE = new Cartesian3(); @@ -742,7 +731,6 @@ define([ return GroundPrimitive._initPromise; }; - var geometryRectangleScratch = new Rectangle(); /** * Called when {@link Viewer} or {@link CesiumWidget} render the scene to * get the draw commands needed to render this primitive. @@ -850,16 +838,14 @@ define([ instanceType = geometry.constructor; var boundingRectangle = getRectangle(frameState, geometry); - var geometryRectangle = geometryRectangleScratch; - getUnrotatedRectangle(ellipsoid, geometry, boundingRectangle, geometryRectangle); - var geometryRotation = getGeometryRotation(geometry); - var texcoordRotation = geometry._stRotation; + var unrotatedTextureRectangle = geometry.unrotatedTextureRectangle; + var texcoordRotation = getStRotation(geometry); if (!allSameColor) { if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, geometryRectangle, ellipsoid, frameState.mapProjection, this._maxHeight, texcoordRotation, geometryRotation); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, this._maxHeight, texcoordRotation); } else { - attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, geometryRectangle, ellipsoid, frameState.mapProjection, texcoordRotation, geometryRotation); + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, texcoordRotation); } } else { attributes = {}; diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 7d997dc2dc0..5fa247c5f2e 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -332,83 +332,44 @@ define([ 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()]; - var rotatedPoint2DScratch = new Cartesian2(); + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2()]; var rotation2DScratch = new Matrix2(); - var min2DScratch = new Cartesian2(); - var max2DScratch = new Cartesian2(); var rectangleCenterScratch = new Cartographic(); - // boundingRectangle is a rectangle around the rotated geometry while geometryRectangle is the bounding box around the unrotated geometry. - function addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation) { + // boundingRectangle is a rectangle around the rotated geometry while unrotatedTextureRectangle is the bounding box around the unrotated geometry. + function addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation) { textureCoordinateRotation = defaultValue(textureCoordinateRotation, 0.0); - geometryRotation = defaultValue(geometryRotation, 0.0); - - var i; - var point2D; + var points2D = points2DScratch; // Assume a computed "east-north" texture coordinate system. - // The "desired" texture coordinate system forms a rectangle around the geometryRectangle that completely bounds it. - // Both the geometry and the "desired" texture coordinate system rectangles may be rotated. - // Compute the corners of the "desired" texture coordinate system in "east-north" texture space by the following: - // - Treat all rectangles as if the centers are at 0, 0 - // - rotate the geometryRectangle by geometryRotation - textureCoordinateRotation - // - compute corners of box bounding transformed geometryRectangle. Call this "desired local" - // - rotate 3 of the corners in "desired local" by textureCoordinateRotation, then compute the equivalent of - // each point in the "east-north" texture coordinate system. - - // Perform all computations in cartographic coordinates, no need to project. - - var northSouthHalfDistance = geometryRectangle.height * 0.5; - var eastWestHalfDistance = geometryRectangle.width * 0.5; - var boundingRectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); - - var points2D = points2DScratch; - points2D[0].x = eastWestHalfDistance; - points2D[0].y = northSouthHalfDistance; - - points2D[1].x = -eastWestHalfDistance; - points2D[1].y = northSouthHalfDistance; - - points2D[2].x = eastWestHalfDistance; - points2D[2].y = -northSouthHalfDistance; - - points2D[3].x = -eastWestHalfDistance; - points2D[3].y = -northSouthHalfDistance; - - var min2D = min2DScratch; - min2D.x = Number.POSITIVE_INFINITY; - min2D.y = Number.POSITIVE_INFINITY; - var max2D = max2DScratch; - max2D.x = Number.NEGATIVE_INFINITY; - max2D.y = Number.NEGATIVE_INFINITY; - - var toDesiredLocal = Matrix2.fromRotation(geometryRotation - textureCoordinateRotation, rotation2DScratch); - for (i = 0; i < 4; ++i) { - point2D = Matrix2.multiplyByVector(toDesiredLocal, points2D[i], rotatedPoint2DScratch); - Cartesian2.minimumByComponent(point2D, min2D, min2D); - Cartesian2.maximumByComponent(point2D, max2D, max2D); - } + // The "desired" texture coordinate system forms a rectangle around the geometry that completely bounds it. + // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following: + // - rotate 3 of the corners in unrotatedTextureRectangle by textureCoordinateRotation around the center of both rectangles + // - compute the equivalent of each point in the "east-north" texture system - points2D[0].x = min2D.x; - points2D[0].y = min2D.y; + points2D[0].x = unrotatedTextureRectangle.west; + points2D[0].y = unrotatedTextureRectangle.south; - points2D[1].x = min2D.x; - points2D[1].y = max2D.y; + points2D[1].x = unrotatedTextureRectangle.west; + points2D[1].y = unrotatedTextureRectangle.north; - points2D[2].x = max2D.x; - points2D[2].y = min2D.y; + points2D[2].x = unrotatedTextureRectangle.east; + points2D[2].y = unrotatedTextureRectangle.south; var toDesiredInComputed = Matrix2.fromRotation(textureCoordinateRotation, rotation2DScratch); - northSouthHalfDistance = geometryRectangle.height * 0.5; - eastWestHalfDistance = geometryRectangle.width * 0.5; + var boundingRectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); - for (i = 0; i < 3; ++i) { - point2D = points2D[i]; + for (var i = 0; i < 3; ++i) { + var point2D = points2D[i]; + point2D.x -= boundingRectangleCenter.longitude; + point2D.y -= boundingRectangleCenter.latitude; Matrix2.multiplyByVector(toDesiredInComputed, point2D, point2D); + point2D.x += boundingRectangleCenter.longitude; + point2D.y += boundingRectangleCenter.latitude; + // Convert point into east-north texture coordinate space - point2D.x = (point2D.x + boundingRectangleCenter.longitude - boundingRectangle.west) / boundingRectangle.width; - point2D.y = (point2D.y + boundingRectangleCenter.latitude - boundingRectangle.south) / boundingRectangle.height; + point2D.x = (point2D.x - boundingRectangle.west) / boundingRectangle.width; + point2D.y = (point2D.y - boundingRectangle.south) / boundingRectangle.height; } // Encode these points in the batch table. @@ -606,18 +567,17 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound - * @param {Rectangle} geometryRectangle If the geometry can be rotated, the bounding rectangle for the unrotated geometry. + * @param {Rectangle} unrotatedTextureRectangle TODO: doc * @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. * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation - * @param {Number} [geometryRotation] If the geometry can be rotated, the rotation in radians. * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(boundingRectangle, geometryRectangle, ellipsoid, projection, height, textureCoordinateRotation, geometryRotation) { + ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(boundingRectangle, unrotatedTextureRectangle, ellipsoid, projection, height, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); - Check.typeOf.object('geometryRectangle', geometryRectangle); + Check.typeOf.object('unrotatedTextureRectangle', unrotatedTextureRectangle); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); @@ -628,7 +588,7 @@ define([ computeRectangleBounds(boundingRectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); var attributes = {}; - addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation); + addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation); var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch); attributes.southWest_HIGH = new GeometryInstanceAttribute({ @@ -697,17 +657,16 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound - * @param {Rectangle} geometryRectangle If the geometry can be rotated, the bounding rectangle for the unrotated geometry. + * @param {Rectangle} unrotatedTextureRectangle TODO: doc * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation - * @param {Number} [geometryRotation] If the geometry can be rotated, the rotation in radians. * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. */ - ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, geometryRectangle, ellipsoid, projection, textureCoordinateRotation, geometryRotation) { + ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, unrotatedTextureRectangle, ellipsoid, projection, textureCoordinateRotation) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); - Check.typeOf.object('geometryRectangle', geometryRectangle); + Check.typeOf.object('unrotatedTextureRectangle', unrotatedTextureRectangle); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); @@ -735,7 +694,7 @@ define([ }) }; - addTextureCoordinateRotationAttributes(attributes, boundingRectangle, geometryRectangle, textureCoordinateRotation, geometryRotation); + addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation); add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); return attributes; }; From 41236c45b65914ed6b07ba448d38693f7b5d8ff0 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 9 May 2018 12:12:36 -0400 Subject: [PATCH 32/40] add specs for tight oriented rectangles for texture coordinates --- Source/Core/CircleGeometry.js | 8 +++++++- Source/Core/CorridorGeometry.js | 6 ++++++ Source/Core/EllipseGeometry.js | 3 +++ Source/Core/PolygonGeometry.js | 3 +++ Source/Core/RectangleGeometry.js | 3 +++ Source/Scene/ShadowVolumeAppearance.js | 12 +++++++----- Specs/Core/CircleGeometrySpec.js | 17 +++++++++++++++++ Specs/Core/CorridorGeometrySpec.js | 22 ++++++++++++++++++++++ Specs/Core/EllipseGeometrySpec.js | 16 ++++++++++++++++ Specs/Core/PolygonGeometrySpec.js | 23 +++++++++++++++++++++++ Specs/Core/RectangleGeometrySpec.js | 16 ++++++++++++++++ Specs/Scene/ShadowVolumeAppearanceSpec.js | 4 ++-- 12 files changed, 125 insertions(+), 8 deletions(-) diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js index 677605a836e..5610c5eef18 100644 --- a/Source/Core/CircleGeometry.js +++ b/Source/Core/CircleGeometry.js @@ -186,11 +186,17 @@ define([ } }, /** + * For stRotation on GroundPrimitives. + * Returns the rectangle part of an oriented rectangle that tightly bounds + * the oriented geometry in Cartographic space. + * + * For circles the orientation doesn't matter, so just return the rectangle. + * * @private */ unrotatedTextureRectangle : { get : function() { - return this._ellipseGeometry.unrotatedTextureRectangle; + return this._ellipseGeometry.rectangle; } } }); diff --git a/Source/Core/CorridorGeometry.js b/Source/Core/CorridorGeometry.js index 2f02a225777..cd6cb7eb45e 100644 --- a/Source/Core/CorridorGeometry.js +++ b/Source/Core/CorridorGeometry.js @@ -1047,6 +1047,12 @@ define([ } }, /** + * For stRotation on GroundPrimitives. + * Returns the rectangle part of an oriented rectangle that tightly bounds + * the oriented geometry in Cartographic space. + * + * Corridors don't support geometry orientation or stRotation, + * so just return the rectangle. * @private */ unrotatedTextureRectangle : { diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index 697ba98cc62..a1e86c43417 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -988,6 +988,9 @@ define([ } }, /** + * For stRotation on GroundPrimitives. + * Returns the rectangle part of an oriented rectangle that tightly bounds + * the oriented geometry in Cartographic space. * @private */ unrotatedTextureRectangle : { diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 20d1882353e..d12c3929dc1 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -958,6 +958,9 @@ define([ } }, /** + * For stRotation on GroundPrimitives. + * Returns the rectangle part of an oriented rectangle that tightly bounds + * the oriented geometry in Cartographic space. * @private */ unrotatedTextureRectangle : { diff --git a/Source/Core/RectangleGeometry.js b/Source/Core/RectangleGeometry.js index 8de4a0c1f7a..3b0fa86d80b 100644 --- a/Source/Core/RectangleGeometry.js +++ b/Source/Core/RectangleGeometry.js @@ -894,6 +894,9 @@ define([ } }, /** + * For stRotation on GroundPrimitives. + * Returns the rectangle part of an oriented rectangle that tightly bounds + * the oriented geometry in Cartographic space. * @private */ unrotatedTextureRectangle : { diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 5fa247c5f2e..9b183eabb5e 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -341,11 +341,13 @@ define([ textureCoordinateRotation = defaultValue(textureCoordinateRotation, 0.0); var points2D = points2DScratch; - // Assume a computed "east-north" texture coordinate system. - // The "desired" texture coordinate system forms a rectangle around the geometry that completely bounds it. - // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following: - // - rotate 3 of the corners in unrotatedTextureRectangle by textureCoordinateRotation around the center of both rectangles - // - compute the equivalent of each point in the "east-north" texture system + // Assume a computed "east-north" texture coordinate system based on spherical or planar tricks, bounded by `boundingRectangle`. + // The "desired" texture coordinate system forms an oriented rectangle (un-oriented provided) around the geometry that completely and tightly bounds it. + // We want to map from the "east-north" texture coordinate system into the "desired" system using a pair of lines (analagous planes in 2D) + // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following in cartographic space: + // - rotate 3 of the corners in unrotatedTextureRectangle by textureCoordinateRotation around the center of both rectangles (which should be the same) + // - apply the "east-north" system's normalization formula to the rotated cartographics, even though this is likely to produce values outside [0-1]. + // This gives us a set of points in the "east-north" texture coordinate system that can be used to map "east-north" texture coordinates to "desired." points2D[0].x = unrotatedTextureRectangle.west; points2D[0].y = unrotatedTextureRectangle.south; diff --git a/Specs/Core/CircleGeometrySpec.js b/Specs/Core/CircleGeometrySpec.js index a9a9c04ee13..1eefc41f0c8 100644 --- a/Specs/Core/CircleGeometrySpec.js +++ b/Specs/Core/CircleGeometrySpec.js @@ -164,6 +164,23 @@ defineSuite([ expect(r.west).toEqual(-1.3196344953554853); }); + it('computing unrotatedTextureRectangle property', function() { + var center = Cartesian3.fromDegrees(-75.59777, 40.03883); + var ellipse = new CircleGeometry({ + center : center, + radius : 1000.0, + stRotation : CesiumMath.toRadians(45) + }); + + // Should be the same as rectangle property + var unrotatedTextureRectangle = ellipse.unrotatedTextureRectangle; + var rectangle = ellipse.rectangle; + expect(unrotatedTextureRectangle.north).toEqual(rectangle.north); + expect(unrotatedTextureRectangle.south).toEqual(rectangle.south); + expect(unrotatedTextureRectangle.east).toEqual(rectangle.east); + expect(unrotatedTextureRectangle.west).toEqual(rectangle.west); + }); + var center = Cartesian3.fromDegrees(0,0); var ellipsoid = Ellipsoid.WGS84; var packableInstance = new CircleGeometry({ diff --git a/Specs/Core/CorridorGeometrySpec.js b/Specs/Core/CorridorGeometrySpec.js index b338288504a..807878f7af9 100644 --- a/Specs/Core/CorridorGeometrySpec.js +++ b/Specs/Core/CorridorGeometrySpec.js @@ -281,6 +281,28 @@ defineSuite([ expect(CesiumMath.toDegrees(r.west)).toEqual(-67.6550047734171); }); + it('computing unrotatedTextureRectangle property', function() { + var c = new CorridorGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + positions : Cartesian3.fromDegreesArray([ + -67.655, 0.0, + -67.655, 15.0, + -67.655, 20.0 + ]), + cornerType: CornerType.MITERED, + width : 1, + granularity : Math.PI / 6.0 + }); + + // Corridors don't support geometry orientation or stRotation, so expect this to equal the standard rectangle. + var unrotatedTextureRectangle = c.unrotatedTextureRectangle; + var rectangle = c.rectangle; + expect(unrotatedTextureRectangle.north).toEqual(rectangle.north); + expect(unrotatedTextureRectangle.south).toEqual(rectangle.south); + expect(unrotatedTextureRectangle.east).toEqual(rectangle.east); + expect(unrotatedTextureRectangle.west).toEqual(rectangle.west); + }); + var positions = Cartesian3.fromDegreesArray([ 90.0, -30.0, 90.0, -31.0 diff --git a/Specs/Core/EllipseGeometrySpec.js b/Specs/Core/EllipseGeometrySpec.js index 57ef668ed30..d1f4d0e29f0 100644 --- a/Specs/Core/EllipseGeometrySpec.js +++ b/Specs/Core/EllipseGeometrySpec.js @@ -264,6 +264,22 @@ defineSuite([ expect(r.west).toEqualEpsilon(-CesiumMath.PI, CesiumMath.EPSILON7); }); + it('computing unrotatedTextureRectangle property', function() { + var center = Cartesian3.fromDegrees(0, 0); + var ellipse = new EllipseGeometry({ + center : center, + semiMajorAxis : 2000.0, + semiMinorAxis : 1000.0, + stRotation : CesiumMath.toRadians(45) + }); + + var r = ellipse.unrotatedTextureRectangle; + expect(r.north).toEqualEpsilon(0.00024947054494468227 , CesiumMath.EPSILON7); + expect(r.south).toEqualEpsilon(-0.00024947054494468227, CesiumMath.EPSILON7); + expect(r.east).toEqualEpsilon(0.00024780049692545245, CesiumMath.EPSILON7); + expect(r.west).toEqualEpsilon(-0.00024780049692545245, CesiumMath.EPSILON7); + }); + var center = Cartesian3.fromDegrees(0,0); var ellipsoid = Ellipsoid.WGS84; var packableInstance = new EllipseGeometry({ diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js index 645370d55fc..63634cc5d4c 100644 --- a/Specs/Core/PolygonGeometrySpec.js +++ b/Specs/Core/PolygonGeometrySpec.js @@ -647,6 +647,29 @@ defineSuite([ expect(CesiumMath.toDegrees(r.west)).toEqualEpsilon(-100.5, CesiumMath.EPSILON13); }); + it('computing unrotatedTextureRectangle property', function() { + var p = new PolygonGeometry({ + vertexFormat : VertexFormat.POSITION_AND_ST, + polygonHierarchy: { + positions : Cartesian3.fromDegreesArrayHeights([ + -10.0, -10.0, 0, + -10.0, 10.0, 0, + 10.0, -10.0, 0, + 10.0, 10.0, 0 + ])}, + granularity: CesiumMath.PI, + stRotation : CesiumMath.toRadians(45) + }); + + var r = p.unrotatedTextureRectangle; + // Rotated should be a square + var expectedExtent = 10.0 * Math.sqrt(2.0); + expect(CesiumMath.toDegrees(r.north)).toEqualEpsilon(expectedExtent, CesiumMath.EPSILON7); + expect(CesiumMath.toDegrees(r.south)).toEqualEpsilon(-expectedExtent, CesiumMath.EPSILON7); + expect(CesiumMath.toDegrees(r.east)).toEqualEpsilon(expectedExtent, CesiumMath.EPSILON7); + expect(CesiumMath.toDegrees(r.west)).toEqualEpsilon(-expectedExtent, CesiumMath.EPSILON7); + }); + var positions = Cartesian3.fromDegreesArray([ -12.4, 3.5, -12.0, 3.5, diff --git a/Specs/Core/RectangleGeometrySpec.js b/Specs/Core/RectangleGeometrySpec.js index 53afd99d0c8..dbca618cf53 100644 --- a/Specs/Core/RectangleGeometrySpec.js +++ b/Specs/Core/RectangleGeometrySpec.js @@ -313,6 +313,22 @@ defineSuite([ expect(CesiumMath.toDegrees(r.west)).toEqual(-1.4142135623730951); }); + it('computing unrotatedTextureRectangle property', function() { + var rectangle = new Rectangle.fromDegrees(-1.0, -1.0, 1.0, 1.0); + var geometry = new RectangleGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + rectangle : rectangle, + granularity : 1.0, + stRotation : CesiumMath.toRadians(45.0) + }); + + var r = geometry.unrotatedTextureRectangle; + expect(CesiumMath.toDegrees(r.north)).toEqual(1.414213562373095); + expect(CesiumMath.toDegrees(r.south)).toEqual(-1.414213562373095); + expect(CesiumMath.toDegrees(r.east)).toEqual(1.414213562373095); + expect(CesiumMath.toDegrees(r.west)).toEqual(-1.4142135623730951); + }); + it('computing rectangle property with zero rotation', function() { expect(function() { return RectangleGeometry.createGeometry(new RectangleGeometry({ diff --git a/Specs/Scene/ShadowVolumeAppearanceSpec.js b/Specs/Scene/ShadowVolumeAppearanceSpec.js index 1df6d78e9e4..36779f73f28 100644 --- a/Specs/Scene/ShadowVolumeAppearanceSpec.js +++ b/Specs/Scene/ShadowVolumeAppearanceSpec.js @@ -181,14 +181,14 @@ defineSuite([ expect(uvMinAndExtents.componentsPerAttribute).toEqual(4); expect(uvMinAndExtents.normalize).toEqual(false); - // 90 degree rotation of a square, so "max" point in Y direction is 0,0, "max" point in X direction is 1,1 + // 90 degree rotation of a square, so "max" in Y direction is (0,0), "max" in X direction is (1,1) var value = uMaxVmax.value; expect(value[0]).toEqual(0.0); expect(value[1]).toEqual(0.0); expect(value[2]).toEqual(1.0); expect(value[3]).toEqual(1.0); - // So "min" of texture coordinates is at 1, 0 and extents are just 1s + // So "min" of texture coordinates is at (1,0) and extents are just 1s value = uvMinAndExtents.value; expect(value[0]).toEqual(1.0); expect(value[1]).toEqual(0.0); From 921a3db5f61249c7aa4d8d9a76312b45a497bbf9 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 9 May 2018 15:29:28 -0400 Subject: [PATCH 33/40] fix fallback picking --- Source/Scene/ClassificationPrimitive.js | 46 +++++++- Source/Scene/GroundPrimitive.js | 123 +++++++++++++-------- Specs/Scene/ClassificationPrimitiveSpec.js | 61 ++++++++++ Specs/Scene/GroundPrimitiveSpec.js | 62 +++++++++++ 4 files changed, 242 insertions(+), 50 deletions(-) diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index f21895b32d2..d81a82fdbe4 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -206,6 +206,8 @@ define([ this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; + this._usePickOffsets = false; + this._primitiveOptions = { geometryInstances : undefined, appearance : undefined, @@ -764,8 +766,20 @@ define([ } function createPickCommands(classificationPrimitive, pickCommands) { + var usePickOffsets = classificationPrimitive._usePickOffsets; + var primitive = classificationPrimitive._primitive; var length = primitive._va.length * 3; // each geometry (pack of vertex attributes) needs 3 commands: front/back stencils and fill + + // Fallback for batching same-color geometry instances + var pickOffsets; + var pickIndex = 0; + var pickOffset; + if (usePickOffsets) { + pickOffsets = primitive._pickOffsets; + length = pickOffsets.length * 3; + } + pickCommands.length = length; var j; @@ -777,6 +791,10 @@ define([ for (j = 0; j < length; j += 3) { var vertexArray = primitive._va[vaIndex++]; + if (usePickOffsets) { + pickOffset = pickOffsets[pickIndex++]; + vertexArray = primitive._va[pickOffset.index]; + } // stencil preload command command = pickCommands[j]; @@ -791,6 +809,10 @@ define([ command.renderState = classificationPrimitive._rsStencilPreloadPass; command.shaderProgram = classificationPrimitive._sp; command.uniformMap = uniformMap; + if (usePickOffsets) { + command.offset = pickOffset.offset; + command.count = pickOffset.count; + } // stencil depth command command = pickCommands[j + 1]; @@ -805,6 +827,10 @@ define([ command.renderState = classificationPrimitive._rsStencilDepthPass; command.shaderProgram = classificationPrimitive._sp; command.uniformMap = uniformMap; + if (usePickOffsets) { + command.offset = pickOffset.offset; + command.count = pickOffset.count; + } // pick color command command = pickCommands[j + 2]; @@ -819,6 +845,10 @@ define([ command.renderState = classificationPrimitive._rsPickPass; command.shaderProgram = classificationPrimitive._spPick; command.uniformMap = uniformMap; + if (usePickOffsets) { + command.offset = pickOffset.offset; + command.count = pickOffset.count; + } // derive for 2D if texture coordinates are ever computed if (needs2DShader) { @@ -956,6 +986,8 @@ define([ var attributes; var hasPerColorAttribute = false; + var allColorsSame = true; + var firstColor; var hasSphericalExtentsAttribute = false; var hasPlanarExtentsAttributes = false; @@ -965,12 +997,13 @@ define([ // So don't check for mismatch. hasSphericalExtentsAttribute = ShadowVolumeAppearance.hasAttributesForSphericalExtents(attributes); hasPlanarExtentsAttributes = ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(attributes); + firstColor = attributes.color; } for (i = 0; i < length; i++) { instance = instances[i]; - attributes = instance.attributes; - if (defined(attributes.color)) { + var color = instance.attributes.color; + if (defined(color)) { hasPerColorAttribute = true; } //>>includeStart('debug', pragmas.debug); @@ -978,6 +1011,14 @@ define([ throw new DeveloperError('All GeometryInstances must have color attributes to use per-instance color.'); } //>>includeEnd('debug'); + + allColorsSame = allColorsSame && defined(color) && ColorGeometryInstanceAttribute.equals(firstColor, color); + } + + // If no attributes exist for computing spherical extents or fragment culling, + // throw if the colors aren't all the same. + if (!allColorsSame && !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes) { + throw new DeveloperError('All GeometryInstances must have the same color attribute except via GroundPrimitives'); } // default to a color appearance @@ -997,6 +1038,7 @@ define([ } //>>includeEnd('debug'); + this._usePickOffsets = !hasSphericalExtentsAttribute && !hasPlanarExtentsAttributes; this._hasSphericalExtentsAttribute = hasSphericalExtentsAttribute; this._hasPlanarExtentsAttributes = hasPlanarExtentsAttributes; this._hasPerColorAttribute = hasPerColorAttribute; diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index e4cd29293f4..58d4ef957b0 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -253,6 +253,8 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; + this._useFragmentCulling = false; + var that = this; this._classificationPrimitiveOptions = { geometryInstances : undefined, @@ -686,6 +688,13 @@ define([ if (passes.pick) { var pickLength = pickCommands.length; + + var pickOffsets; + if (!groundPrimitive._useFragmentCulling) { + // Must be using pick offsets + classificationPrimitive = groundPrimitive._primitive; + pickOffsets = classificationPrimitive._primitive._pickOffsets; + } for (var j = 0; j < pickLength; ++j) { var pickCommand = pickCommands[j]; @@ -695,10 +704,15 @@ define([ classificationPrimitive._needs2DShader) { pickCommand = pickCommand.derivedCommands.pick2D; } + var bv = boundingVolumes[boundingVolumeIndex(j, pickLength)]; + if (!groundPrimitive._useFragmentCulling) { + var pickOffset = pickOffsets[boundingVolumeIndex(j, pickLength)]; + bv = boundingVolumes[pickOffset.index]; + } pickCommand.owner = groundPrimitive; pickCommand.modelMatrix = modelMatrix; - pickCommand.boundingVolume = boundingVolumes[boundingVolumeIndex(j, pickLength)]; + pickCommand.boundingVolume = bv; pickCommand.cull = cull; pickCommand.pass = pass; @@ -806,64 +820,66 @@ define([ this._minHeight = this._minTerrainHeight * exaggeration; this._maxHeight = this._maxTerrainHeight * exaggeration; - // Determine whether to add spherical or planar extent attributes for computing texture coordinates. - // This depends on the size of the GeometryInstances. - - // If all the GeometryInstances have the same per-instance color, - // don't bother with texture coordinates at all. - var allSameColor = false; - var color; - var attributes; - if (length > 0 && defined(instances[0].attributes) && defined(instances[0].attributes.color)) { - color = instances[0].attributes.color; - allSameColor = true; - } - var usePlanarExtents = true; - for (i = 0; i < length; ++i) { - instance = instances[i]; - geometry = instance.geometry; - rectangle = getRectangle(frameState, geometry); - if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) { - usePlanarExtents = false; - } - attributes = instance.attributes; - if (defined(color) && defined(attributes) && defined(attributes.color)) { - allSameColor = allSameColor && ColorGeometryInstanceAttribute.equals(color, attributes.color); + var useFragmentCulling = GroundPrimitive._supportsMaterials(frameState.context); + this._useFragmentCulling = useFragmentCulling; + + if (useFragmentCulling) { + // Determine whether to add spherical or planar extent attributes for computing texture coordinates. + // This depends on the size of the GeometryInstances. + var attributes; + var usePlanarExtents = true; + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometry = instance.geometry; + rectangle = getRectangle(frameState, geometry); + if (ShadowVolumeAppearance.shouldUseSphericalCoordinates(rectangle)) { + usePlanarExtents = false; + break; + } } - } - for (i = 0; i < length; ++i) { - instance = instances[i]; - geometry = instance.geometry; - instanceType = geometry.constructor; + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometry = instance.geometry; + instanceType = geometry.constructor; - var boundingRectangle = getRectangle(frameState, geometry); - var unrotatedTextureRectangle = geometry.unrotatedTextureRectangle; - var texcoordRotation = getStRotation(geometry); + var boundingRectangle = getRectangle(frameState, geometry); + var unrotatedTextureRectangle = geometry.unrotatedTextureRectangle; + var texcoordRotation = getStRotation(geometry); - if (!allSameColor) { if (usePlanarExtents) { attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, this._maxHeight, texcoordRotation); } else { attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, texcoordRotation); } - } else { - attributes = {}; - } - var instanceAttributes = instance.attributes; - for (var attributeKey in instanceAttributes) { - if (instanceAttributes.hasOwnProperty(attributeKey)) { - attributes[attributeKey] = instanceAttributes[attributeKey]; + var instanceAttributes = instance.attributes; + for (var attributeKey in instanceAttributes) { + if (instanceAttributes.hasOwnProperty(attributeKey)) { + attributes[attributeKey] = instanceAttributes[attributeKey]; + } } - } - groundInstances[i] = new GeometryInstance({ - geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), - getComputeMaximumHeightFunction(this)), - attributes : attributes, - id : instance.id - }); + groundInstances[i] = new GeometryInstance({ + geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), + getComputeMaximumHeightFunction(this)), + attributes : attributes, + id : instance.id + }); + } + } else { + // ClassificationPrimitive will check if the colors are all the same if it detects lack of fragment culling attributes + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometry = instance.geometry; + instanceType = geometry.constructor; + groundInstances[i] = new GeometryInstance({ + geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), + getComputeMaximumHeightFunction(this)), + attributes : instance.attributes, + id : instance.id + }); + } } primitiveOptions.geometryInstances = groundInstances; @@ -970,6 +986,17 @@ define([ return destroyObject(this); }; + /** + * Exposed for testing. + * + * @param {Context} context Rendering context + * @returns {Boolean} Whether or not the current context supports materials on GroundPrimitives. + * @private + */ + GroundPrimitive._supportsMaterials = function(context) { + return context.depthTexture; + }; + /** * Checks if the given Scene supports materials on GroundPrimitives. * Materials on GroundPrimitives require support for the WEBGL_depth_texture extension. @@ -982,7 +1009,7 @@ define([ Check.typeOf.object('scene', scene); //>>includeEnd('debug'); - return scene.frameState.context.depthTexture; + return GroundPrimitive._supportsMaterials(scene.frameState.context); }; return GroundPrimitive; diff --git a/Specs/Scene/ClassificationPrimitiveSpec.js b/Specs/Scene/ClassificationPrimitiveSpec.js index 41b5ffabb59..ba2dcdef2c2 100644 --- a/Specs/Scene/ClassificationPrimitiveSpec.js +++ b/Specs/Scene/ClassificationPrimitiveSpec.js @@ -778,6 +778,67 @@ defineSuite([ }); }); + it('update throws when batched instance colors are different and no culling attributes are provided', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + var neCarto = Rectangle.northeast(rectangle); + var nwCarto = Rectangle.northwest(rectangle); + + var ne = ellipsoid.cartographicToCartesian(neCarto); + var nw = ellipsoid.cartographicToCartesian(nwCarto); + + var direction = Cartesian3.subtract(ne, nw, new Cartesian3()); + var distance = Cartesian3.magnitude(direction) * 0.25; + Cartesian3.normalize(direction, direction); + Cartesian3.multiplyByScalar(direction, distance, direction); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + + var origin1 = Cartesian3.add(origin, direction, new Cartesian3()); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin1); + + var dimensions = new Cartesian3(500000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var boxInstance1 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box1', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)) + } + }); + + Cartesian3.negate(direction, direction); + var origin2 = Cartesian3.add(origin, direction, new Cartesian3()); + modelMatrix = Transforms.eastNorthUpToFixedFrame(origin2); + + var boxInstance2 = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box2', + attributes : { + color : ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 0.0, 1.0, 1.0)) + } + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + + expect(function() { + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }).toThrowDeveloperError(); + }); + it('update throws when one batched instance color is undefined', function() { if (!ClassificationPrimitive.isSupported(scene)) { return; diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index ea0a1b404cc..e6a7d2c8f64 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -866,6 +866,27 @@ defineSuite([ }); }); + it('picking without depth texture', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + + spyOn(GroundPrimitive, '_supportsMaterials').and.callFake(function() { + return false; + }); + + primitive = new GroundPrimitive({ + geometryInstances : rectangleInstance, + asynchronous : false + }); + + verifyGroundPrimitiveRender(primitive, rectColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('rectangle'); + }); + }); + it('does not pick when allowPicking is false', function() { if (!GroundPrimitive.isSupported(scene)) { return; @@ -949,6 +970,47 @@ defineSuite([ }); }); + it('update throws when batched instance colors are different and materials on GroundPrimitives are not supported', function() { + if (!GroundPrimitive.isSupported(scene)) { + return; + } + spyOn(GroundPrimitive, '_supportsMaterials').and.callFake(function() { + return false; + }); + + var rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var rectangleInstance1 = new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : new Rectangle(rectangle.west, rectangle.south, rectangle.east, (rectangle.north + rectangle.south) * 0.5) + }), + id : 'rectangle1', + attributes : { + color : rectColorAttribute + } + }); + rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); + var rectangleInstance2 = new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : new Rectangle(rectangle.west, (rectangle.north + rectangle.south) * 0.5, rectangle.east, rectangle.north) + }), + id : 'rectangle2', + attributes : { + color : rectColorAttribute + } + }); + + primitive = new GroundPrimitive({ + geometryInstances : [rectangleInstance1, rectangleInstance2], + asynchronous : false + }); + + expect(function() { + verifyGroundPrimitiveRender(primitive, rectColorAttribute.value); + }).toThrowDeveloperError(); + }); + it('update throws when one batched instance color is undefined', function() { if (!GroundPrimitive.isSupported(scene)) { return; From 75dcb60eede450acaf502e676f4fdabd495c066d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 10 May 2018 08:10:12 -0400 Subject: [PATCH 34/40] fix test failures with webgl stub for StaticGroundGeometryPerMaterialBatch --- ...taticGroundGeometryPerMaterialBatchSpec.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js index ba6fd15bb2f..67797e85a23 100644 --- a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js +++ b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js @@ -20,6 +20,7 @@ defineSuite([ 'DataSources/PolylineGraphics', 'DataSources/TimeIntervalCollectionProperty', 'Scene/ClassificationType', + 'Scene/GroundPrimitive', 'Scene/MaterialAppearance', 'Scene/PolylineColorAppearance', 'Scene/PolylineMaterialAppearance', @@ -47,6 +48,7 @@ defineSuite([ PolylineGraphics, TimeIntervalCollectionProperty, ClassificationType, + GroundPrimitive, MaterialAppearance, PolylineColorAppearance, PolylineMaterialAppearance, @@ -65,6 +67,11 @@ defineSuite([ }); it('handles shared material being invalidated with geometry', function() { + if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + // Don't fail if materials on GroundPrimitive not supported + return; + } + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); var ellipse = new EllipseGraphics(); @@ -114,6 +121,11 @@ defineSuite([ }); it('updates with sampled distance display condition out of range', function() { + if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + // Don't fail if materials on GroundPrimitive not supported + return; + } + var validTime = JulianDate.fromIso8601('2018-02-14T04:10:00+1100'); var ddc = new TimeIntervalCollectionProperty(); ddc.intervals.addInterval(TimeInterval.fromIso8601({ @@ -159,6 +171,11 @@ defineSuite([ }); it('shows only one primitive while rebuilding primitive', function() { + if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + // Don't fail if materials on GroundPrimitive not supported + return; + } + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); function buildEntity(x, y, z) { @@ -228,6 +245,11 @@ defineSuite([ }); it('batches overlapping entities with the same material separately', function() { + if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + // Don't fail if materials on GroundPrimitive not supported + return; + } + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); var ellipse = new EllipseGraphics(); @@ -268,6 +290,11 @@ defineSuite([ }); it('batches nonoverlapping entities that both use color materials', function() { + if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + // Don't fail if materials on GroundPrimitive not supported + return; + } + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); var entity = new Entity({ position : new Cartesian3(1234, 5678, 9101112), From fe8d1f7fca0bc56146eda2b1eea0844154638c2f Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 10 May 2018 21:26:27 -0400 Subject: [PATCH 35/40] compute texture coordinate rotation parameters in local ENU for Ellipses and Polygons --- Source/Core/CircleGeometry.js | 11 +- Source/Core/EllipseGeometry.js | 57 +++++---- Source/Core/Geometry.js | 142 +++++++++++++++++++++- Source/Core/PolygonGeometry.js | 51 ++------ Source/Core/RectangleGeometry.js | 106 ++++++++++++---- Source/Scene/GroundPrimitive.js | 18 +-- Source/Scene/ShadowVolumeAppearance.js | 72 +++-------- Specs/Core/CircleGeometrySpec.js | 22 ++-- Specs/Core/EllipseGeometrySpec.js | 34 ++++-- Specs/Core/GeometrySpec.js | 27 +++- Specs/Core/PolygonGeometrySpec.js | 50 ++++++-- Specs/Core/RectangleGeometrySpec.js | 34 ++++-- Specs/Scene/ShadowVolumeAppearanceSpec.js | 10 +- 13 files changed, 416 insertions(+), 218 deletions(-) diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js index 5610c5eef18..457b684546d 100644 --- a/Source/Core/CircleGeometry.js +++ b/Source/Core/CircleGeometry.js @@ -186,17 +186,12 @@ define([ } }, /** - * For stRotation on GroundPrimitives. - * Returns the rectangle part of an oriented rectangle that tightly bounds - * the oriented geometry in Cartographic space. - * - * For circles the orientation doesn't matter, so just return the rectangle. - * + * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. * @private */ - unrotatedTextureRectangle : { + textureCoordinateRotationPoints : { get : function() { - return this._ellipseGeometry.rectangle; + return this._ellipseGeometry.textureCoordinateRotationPoints; } } }); diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index a1e86c43417..eac049ab3cd 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -675,27 +675,6 @@ define([ return rectangle; } - var scratchEllipseGeometry = new EllipseGeometry({ - center : Cartesian3.ZERO, - semiMajorAxis : 2, - semiMinorAxis : 1 - }); - function computeUnrotatedTextureRectangle(ellipseGeometry) { - var rotatedEllipse = scratchEllipseGeometry; - rotatedEllipse._center = Cartesian3.clone(ellipseGeometry._center, rotatedEllipse._center); - rotatedEllipse._semiMajorAxis = ellipseGeometry._semiMajorAxis; - rotatedEllipse._semiMinorAxis = ellipseGeometry._semiMinorAxis; - rotatedEllipse._ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, rotatedEllipse._ellipsoid); - rotatedEllipse._granularity = ellipseGeometry._granularity; - - // Rotate to align the texture coordinates with ENU - // Ellipse texture rotation is backwards, so + instead of - - rotatedEllipse._rotation = ellipseGeometry._rotation + ellipseGeometry._stRotation; - rotatedEllipse._rectangle = undefined; - - return computeRectangle(rotatedEllipse); - } - /** * A description of an ellipse on an ellipsoid. Ellipse geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}. * @@ -975,6 +954,31 @@ define([ }); }; + function textureCoordinateRotationPoints(ellipseGeometry) { + var stRotation = -ellipseGeometry._stRotation; + if (stRotation === 0.0) { + return [0, 0, 0, 1, 1, 0]; + } + + var cep = EllipseGeometryLibrary.computeEllipsePositions({ + center : ellipseGeometry._center, + semiMajorAxis : ellipseGeometry._semiMajorAxis, + semiMinorAxis : ellipseGeometry._semiMinorAxis, + rotation : ellipseGeometry._rotation, + granularity : ellipseGeometry._granularity + }, false, true); + var positionsFlat = cep.outerPositions; + var positionsCount = positionsFlat.length / 3; + var positions = new Array(positionsCount); + for (var i = 0; i < positionsCount; ++i) { + positions[i] = Cartesian3.fromArray(positionsFlat, i * 3); + } + + var ellipsoid = ellipseGeometry._ellipsoid; + var boundingRectangle = ellipseGeometry.rectangle; + return Geometry._textureCoordinateRotationPoints(positions, stRotation, ellipsoid, boundingRectangle); + } + defineProperties(EllipseGeometry.prototype, { /** * @private @@ -988,17 +992,12 @@ define([ } }, /** - * For stRotation on GroundPrimitives. - * Returns the rectangle part of an oriented rectangle that tightly bounds - * the oriented geometry in Cartographic space. + * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. * @private */ - unrotatedTextureRectangle : { + textureCoordinateRotationPoints : { get : function() { - if (!defined(this._unrotatedTextureRectangle)) { - this._unrotatedTextureRectangle = computeUnrotatedTextureRectangle(this); - } - return this._unrotatedTextureRectangle; + return textureCoordinateRotationPoints(this); } } }); diff --git a/Source/Core/Geometry.js b/Source/Core/Geometry.js index 48e8fdd673d..29749d808c3 100644 --- a/Source/Core/Geometry.js +++ b/Source/Core/Geometry.js @@ -1,17 +1,35 @@ define([ + './Cartesian2', + './Cartesian3', + './Cartographic', './Check', './defaultValue', './defined', './DeveloperError', './GeometryType', - './PrimitiveType' + './Matrix2', + './Matrix3', + './Matrix4', + './PrimitiveType', + './Quaternion', + './Rectangle', + './Transforms' ], function( + Cartesian2, + Cartesian3, + Cartographic, Check, defaultValue, defined, DeveloperError, GeometryType, - PrimitiveType) { + Matrix2, + Matrix3, + Matrix4, + PrimitiveType, + Quaternion, + Rectangle, + Transforms) { 'use strict'; /** @@ -195,5 +213,125 @@ define([ return numberOfVertices; }; + var rectangleCenterScratch = new Cartographic(); + var enuCenterScratch = new Cartesian3(); + var fixedFrameToEnuScratch = new Matrix4(); + var boundingRectanglePointsCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic()]; + var boundingRectanglePointsEnuScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2()]; + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2()]; + var pointEnuScratch = new Cartesian3(); + var enuRotationScratch = new Quaternion(); + var enuRotationMatrixScratch = new Matrix4(); + var rotation2DScratch = new Matrix2(); + + /** + * For remapping texture coordinates when rendering GroundPrimitives with materials. + * GroundPrimitive texture coordinates are computed to align with the cartographic coordinate system on the globe. + * However, EllipseGeometry, RectangleGeometry, and PolygonGeometry all bake rotations to per-vertex texture coordinates + * and use different algorithms to do so. + * + * This method is used by EllipseGeometry and PolygonGeometry to approximate the same visual effect. + * We encapsulate rotation and necessary scale by computing a "transformed" texture coordinate system and compute + * a set of points from which "cartographic" texture coordinates can be remapped to the "transformed" system + * using distances to lines in 2D. + * + * @param {Cartesian3[]} positions Array of positions outlining the geometry + * @param {Number} stRotation Texture coordinate rotation. + * @param {Ellipsoid} ellipsoid Ellipsoid for projecting and generating local vectors. + * @param {Rectangle} boundingRectangle Bounding rectangle around the positions. + * @returns {Number[]} An array of 6 numbers specifying [minimum point, u extent, v extent] as points in the "cartographic" system. + * @private + */ + Geometry._textureCoordinateRotationPoints = function(positions, stRotation, ellipsoid, boundingRectangle) { + var i; + + // Create a local east-north-up coordinate system centered on the polygon's bounding rectangle. + // Project the southwest, northwest, and southeast corners of the bounding rectangle into the plane of ENU as 2D points. + // These are the equivalents of (0,0), (0,1), and (1,0) in the texture coordiante system computed in ShadowVolumeAppearanceFS, + // aka "ENU texture space." + var rectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); + var enuCenter = Cartographic.toCartesian(rectangleCenter, ellipsoid, enuCenterScratch); + var enuToFixedFrame = Transforms.eastNorthUpToFixedFrame(enuCenter, ellipsoid, fixedFrameToEnuScratch); + var fixedFrameToEnu = Matrix4.inverse(enuToFixedFrame, fixedFrameToEnuScratch); + + var boundingPointsEnu = boundingRectanglePointsEnuScratch; + var boundingPointsCarto = boundingRectanglePointsCartographicScratch; + + boundingPointsCarto[0].longitude = boundingRectangle.west; + boundingPointsCarto[0].latitude = boundingRectangle.south; + + boundingPointsCarto[1].longitude = boundingRectangle.west; + boundingPointsCarto[1].latitude = boundingRectangle.north; + + boundingPointsCarto[2].longitude = boundingRectangle.east; + boundingPointsCarto[2].latitude = boundingRectangle.south; + + var posEnu = pointEnuScratch; + + for (i = 0; i < 3; i++) { + Cartographic.toCartesian(boundingPointsCarto[i], ellipsoid, posEnu); + posEnu = Matrix4.multiplyByPointAsVector(fixedFrameToEnu, posEnu, posEnu); + boundingPointsEnu[i].x = posEnu.x; + boundingPointsEnu[i].y = posEnu.y; + } + + // Rotate each point in the polygon around the up vector in the ENU by -stRotation and project into ENU as 2D. + // Compute the bounding box of these rotated points in the 2D ENU plane. + // Rotate the corners back by stRotation, then compute their equivalents in the ENU texture space using the corners computed earlier. + var rotation = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -stRotation, enuRotationScratch); + var textureMatrix = Matrix3.fromQuaternion(rotation, enuRotationMatrixScratch); + + var positionsLength = positions.length; + var enuMinX = Number.POSITIVE_INFINITY; + var enuMinY = Number.POSITIVE_INFINITY; + var enuMaxX = Number.NEGATIVE_INFINITY; + var enuMaxY = Number.NEGATIVE_INFINITY; + for (i = 0; i < positionsLength; i++) { + posEnu = Matrix4.multiplyByPointAsVector(fixedFrameToEnu, positions[i], posEnu); + posEnu = Matrix3.multiplyByVector(textureMatrix, posEnu, posEnu); + + enuMinX = Math.min(enuMinX, posEnu.x); + enuMinY = Math.min(enuMinY, posEnu.y); + enuMaxX = Math.max(enuMaxX, posEnu.x); + enuMaxY = Math.max(enuMaxY, posEnu.y); + } + + var toDesiredInComputed = Matrix2.fromRotation(stRotation, rotation2DScratch); + + var points2D = points2DScratch; + points2D[0].x = enuMinX; + points2D[0].y = enuMinY; + + points2D[1].x = enuMinX; + points2D[1].y = enuMaxY; + + points2D[2].x = enuMaxX; + points2D[2].y = enuMinY; + + var boundingEnuMin = boundingPointsEnu[0]; + var boundingPointsWidth = boundingPointsEnu[2].x - boundingEnuMin.x; + var boundingPointsHeight = boundingPointsEnu[1].y - boundingEnuMin.y; + + for (i = 0; i < 3; i++) { + var point2D = points2D[i]; + // rotate back + Matrix2.multiplyByVector(toDesiredInComputed, point2D, point2D); + + // Convert point into east-north texture coordinate space + point2D.x = (point2D.x - boundingEnuMin.x) / boundingPointsWidth; + point2D.y = (point2D.y - boundingEnuMin.y) / boundingPointsHeight; + } + + var minXYCorner = points2D[0]; + var maxYCorner = points2D[1]; + var maxXCorner = points2D[2]; + var result = new Array(6); + Cartesian2.pack(minXYCorner, result); + Cartesian2.pack(maxYCorner, result, 2); + Cartesian2.pack(maxXCorner, result, 4); + + return result; + }; + return Geometry; }); diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index d12c3929dc1..47a3918e947 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -910,33 +910,15 @@ define([ }); }; - var rectangleCenterScratch = new Cartographic(); - var cartographicScratch = new Cartographic(); - var rotationScratch = new Matrix2(); - var cartesian2Scratch = new Cartesian2(); - function computeRectangleRotatedPositions(originalRectangle, positions, ellipsoid, angle) { - var result = new Rectangle(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); - var center = Rectangle.center(originalRectangle, rectangleCenterScratch); - var rotation = Matrix2.fromRotation(angle, rotationScratch); - - var positionsLength = positions.length; - var cartesian2 = cartesian2Scratch; - for (var i = 0; i < positionsLength; i++) { - var cartographic = Cartographic.fromCartesian(positions[i], ellipsoid, cartographicScratch); - cartesian2.x = cartographic.longitude - center.longitude; - cartesian2.y = cartographic.latitude - center.latitude; - - Matrix2.multiplyByVector(rotation, cartesian2, cartesian2); - - cartesian2.x += center.longitude; - cartesian2.y += center.latitude; - - result.west = Math.min(result.west, cartesian2.x); - result.east = Math.max(result.east, cartesian2.x); - result.south = Math.min(result.south, cartesian2.y); - result.north = Math.max(result.north, cartesian2.y); + function textureCoordinateRotationPoints(polygonGeometry) { + var stRotation = -polygonGeometry._stRotation; + if (stRotation === 0.0) { + return [0, 0, 0, 1, 1, 0]; } - return result; + var ellipsoid = polygonGeometry._ellipsoid; + var positions = polygonGeometry._polygonHierarchy.positions; + var boundingRectangle = polygonGeometry.rectangle; + return Geometry._textureCoordinateRotationPoints(positions, stRotation, ellipsoid, boundingRectangle); } defineProperties(PolygonGeometry.prototype, { @@ -958,22 +940,15 @@ define([ } }, /** - * For stRotation on GroundPrimitives. - * Returns the rectangle part of an oriented rectangle that tightly bounds - * the oriented geometry in Cartographic space. + * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. * @private */ - unrotatedTextureRectangle : { + textureCoordinateRotationPoints : { get : function() { - if (!defined(this._unrotatedTextureRectangle)) { - var positions = this._polygonHierarchy.positions; - if (!defined(positions) || positions.length < 3) { - this._unrotatedTextureRectangle = new Rectangle(); - } else { - this._unrotatedTextureRectangle = computeRectangleRotatedPositions(this.rectangle, positions, this._ellipsoid, -this._stRotation); - } + if (!defined(this._textureCoordinateRotationPoints)) { + this._textureCoordinateRotationPoints = textureCoordinateRotationPoints(this); } - return this._unrotatedTextureRectangle; + return this._textureCoordinateRotationPoints; } } }); diff --git a/Source/Core/RectangleGeometry.js b/Source/Core/RectangleGeometry.js index 3b0fa86d80b..675bb978f13 100644 --- a/Source/Core/RectangleGeometry.js +++ b/Source/Core/RectangleGeometry.js @@ -17,6 +17,7 @@ define([ './GeometryPipeline', './IndexDatatype', './Math', + './Matrix2', './Matrix3', './PolygonPipeline', './PrimitiveType', @@ -43,6 +44,7 @@ define([ GeometryPipeline, IndexDatatype, CesiumMath, + Matrix2, Matrix3, PolygonPipeline, PrimitiveType, @@ -580,24 +582,6 @@ define([ return Rectangle.fromCartesianArray(positions, rectangleGeometry._ellipsoid); } - var scratchRectangleGeometry = new RectangleGeometry({ - rectangle : new Rectangle() - }); - function computeUnrotatedTextureRectangle(rectangleGeometry) { - var rotatedRectangle = scratchRectangleGeometry; - - rotatedRectangle._rectangle = Rectangle.clone(rectangleGeometry._rectangle, rotatedRectangle._rectangle); - rotatedRectangle._granularity = rectangleGeometry._granularity; - rotatedRectangle._ellipsoid = Ellipsoid.clone(rectangleGeometry._ellipsoid, rotatedRectangle._ellipsoid); - rotatedRectangle._surfaceHeight = rectangleGeometry._surfaceHeight; - - // Rotate to align the texture coordinates with ENU - rotatedRectangle._rotation = rectangleGeometry._rotation - rectangleGeometry._stRotation; - - var result = computeRectangle(rotatedRectangle); - return Rectangle.clone(result); - } - /** * A description of a cartographic rectangle on an ellipsoid centered at the origin. Rectangle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}. * @@ -670,7 +654,7 @@ define([ this._workerName = 'createRectangleGeometry'; this._rotatedRectangle = undefined; - this._unrotatedTextureRectangle = undefined; + this._textureCoordinateRotationPoints = undefined; } /** @@ -881,6 +865,76 @@ define([ }); }; + var scratchRectangleGeometry = new RectangleGeometry({ + rectangle : new Rectangle() + }); + var unrotatedTextureRectangleScratch = new Rectangle(); + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2()]; + var rotation2DScratch = new Matrix2(); + var rectangleCenterScratch = new Cartographic(); + + function textureCoordinateRotationPoints(rectangleGeometry) { + if (rectangleGeometry._stRotation === 0.0) { + return [0, 0, 0, 1, 1, 0]; + } + // Compute rectangle if rectangleGeometry was rotated so that the texture coordinate system lined up with ENU + var rotatedRectangle = scratchRectangleGeometry; + + rotatedRectangle._rectangle = Rectangle.clone(rectangleGeometry._rectangle, rotatedRectangle._rectangle); + rotatedRectangle._granularity = rectangleGeometry._granularity; + rotatedRectangle._ellipsoid = Ellipsoid.clone(rectangleGeometry._ellipsoid, rotatedRectangle._ellipsoid); + rotatedRectangle._surfaceHeight = rectangleGeometry._surfaceHeight; + + // Rotate to align the texture coordinates with ENU + rotatedRectangle._rotation = rectangleGeometry._rotation - rectangleGeometry._stRotation; + + var unrotatedTextureRectangle = computeRectangle(rotatedRectangle, unrotatedTextureRectangleScratch); + + // Assume a computed "east-north" texture coordinate system based on spherical or planar tricks, bounded by `boundingRectangle`. + // The "desired" texture coordinate system forms an oriented rectangle (un-oriented provided) around the geometry that completely and tightly bounds it. + // We want to map from the "east-north" texture coordinate system into the "desired" system using a pair of lines (analagous planes in 2D) + // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following in cartographic space: + // - rotate 3 of the corners in unrotatedTextureRectangle by stRotation around the center of the bounding rectangle + // - apply the "east-north" system's normalization formula to the rotated cartographics, even though this is likely to produce values outside [0-1]. + // This gives us a set of points in the "east-north" texture coordinate system that can be used to map "east-north" texture coordinates to "desired." + + var points2D = points2DScratch; + points2D[0].x = unrotatedTextureRectangle.west; + points2D[0].y = unrotatedTextureRectangle.south; + + points2D[1].x = unrotatedTextureRectangle.west; + points2D[1].y = unrotatedTextureRectangle.north; + + points2D[2].x = unrotatedTextureRectangle.east; + points2D[2].y = unrotatedTextureRectangle.south; + + var boundingRectangle = rectangleGeometry.rectangle; + var toDesiredInComputed = Matrix2.fromRotation(rectangleGeometry._stRotation, rotation2DScratch); + var boundingRectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); + + for (var i = 0; i < 3; ++i) { + var point2D = points2D[i]; + point2D.x -= boundingRectangleCenter.longitude; + point2D.y -= boundingRectangleCenter.latitude; + Matrix2.multiplyByVector(toDesiredInComputed, point2D, point2D); + point2D.x += boundingRectangleCenter.longitude; + point2D.y += boundingRectangleCenter.latitude; + + // Convert point into east-north texture coordinate space + point2D.x = (point2D.x - boundingRectangle.west) / boundingRectangle.width; + point2D.y = (point2D.y - boundingRectangle.south) / boundingRectangle.height; + } + + var minXYCorner = points2D[0]; + var maxYCorner = points2D[1]; + var maxXCorner = points2D[2]; + var result = new Array(6); + Cartesian2.pack(minXYCorner, result); + Cartesian2.pack(maxYCorner, result, 2); + Cartesian2.pack(maxXCorner, result, 4); + return result; + } + defineProperties(RectangleGeometry.prototype, { /** * @private @@ -894,17 +948,17 @@ define([ } }, /** - * For stRotation on GroundPrimitives. - * Returns the rectangle part of an oriented rectangle that tightly bounds - * the oriented geometry in Cartographic space. + * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. + * This version permits skew in textures by computing offsets in cartographic space. + * @see Geometry#_textureCoordinateRotationPoints * @private */ - unrotatedTextureRectangle : { + textureCoordinateRotationPoints : { get : function() { - if (!defined(this._unrotatedTextureRectangle)) { - this._unrotatedTextureRectangle = computeUnrotatedTextureRectangle(this); + if (!defined(this._textureCoordinateRotationPoints)) { + this._textureCoordinateRotationPoints = textureCoordinateRotationPoints(this); } - return this._unrotatedTextureRectangle; + return this._textureCoordinateRotationPoints; } } }); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 58d4ef957b0..7756c786877 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -11,13 +11,11 @@ define([ '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', - '../Core/EllipseGeometry', '../Core/GeographicTilingScheme', '../Core/GeometryInstance', '../Core/isArray', '../Core/Math', '../Core/OrientedBoundingBox', - '../Core/PolygonGeometry', '../Core/Rectangle', '../Core/RectangleGeometry', '../Core/Resource', @@ -42,13 +40,11 @@ define([ defineProperties, destroyObject, DeveloperError, - EllipseGeometry, GeographicTilingScheme, GeometryInstance, isArray, CesiumMath, OrientedBoundingBox, - PolygonGeometry, Rectangle, RectangleGeometry, Resource, @@ -481,13 +477,6 @@ define([ return rectangle; } - function getStRotation(geometry) { - if ((geometry instanceof PolygonGeometry) || (geometry instanceof EllipseGeometry)) { - return -geometry._stRotation; - } - return geometry._stRotation; - } - var scratchDiagonalCartesianNE = new Cartesian3(); var scratchDiagonalCartesianSW = new Cartesian3(); var scratchDiagonalCartographic = new Cartographic(); @@ -844,13 +833,12 @@ define([ instanceType = geometry.constructor; var boundingRectangle = getRectangle(frameState, geometry); - var unrotatedTextureRectangle = geometry.unrotatedTextureRectangle; - var texcoordRotation = getStRotation(geometry); + var textureCoordinateRotationPoints = geometry.textureCoordinateRotationPoints; if (usePlanarExtents) { - attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, this._maxHeight, texcoordRotation); + attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection, this._maxHeight); } else { - attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, unrotatedTextureRectangle, ellipsoid, frameState.mapProjection, texcoordRotation); + attributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, frameState.mapProjection); } var instanceAttributes = instance.attributes; diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index 9b183eabb5e..ef602fbb573 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -10,7 +10,6 @@ define([ '../Core/defineProperties', '../Core/EncodedCartesian3', '../Core/GeometryInstanceAttribute', - '../Core/Matrix2', '../Core/Matrix4', '../Core/Rectangle', '../Core/Transforms', @@ -29,7 +28,6 @@ define([ defineProperties, EncodedCartesian3, GeometryInstanceAttribute, - Matrix2, Matrix4, Rectangle, Transforms, @@ -332,54 +330,17 @@ define([ 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()]; - var rotation2DScratch = new Matrix2(); - var rectangleCenterScratch = new Cartographic(); + var points2DScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()]; - // boundingRectangle is a rectangle around the rotated geometry while unrotatedTextureRectangle is the bounding box around the unrotated geometry. - function addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation) { - textureCoordinateRotation = defaultValue(textureCoordinateRotation, 0.0); + // 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; - // Assume a computed "east-north" texture coordinate system based on spherical or planar tricks, bounded by `boundingRectangle`. - // The "desired" texture coordinate system forms an oriented rectangle (un-oriented provided) around the geometry that completely and tightly bounds it. - // We want to map from the "east-north" texture coordinate system into the "desired" system using a pair of lines (analagous planes in 2D) - // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following in cartographic space: - // - rotate 3 of the corners in unrotatedTextureRectangle by textureCoordinateRotation around the center of both rectangles (which should be the same) - // - apply the "east-north" system's normalization formula to the rotated cartographics, even though this is likely to produce values outside [0-1]. - // This gives us a set of points in the "east-north" texture coordinate system that can be used to map "east-north" texture coordinates to "desired." - - points2D[0].x = unrotatedTextureRectangle.west; - points2D[0].y = unrotatedTextureRectangle.south; - - points2D[1].x = unrotatedTextureRectangle.west; - points2D[1].y = unrotatedTextureRectangle.north; - - points2D[2].x = unrotatedTextureRectangle.east; - points2D[2].y = unrotatedTextureRectangle.south; - - var toDesiredInComputed = Matrix2.fromRotation(textureCoordinateRotation, rotation2DScratch); - var boundingRectangleCenter = Rectangle.center(boundingRectangle, rectangleCenterScratch); - - for (var i = 0; i < 3; ++i) { - var point2D = points2D[i]; - point2D.x -= boundingRectangleCenter.longitude; - point2D.y -= boundingRectangleCenter.latitude; - Matrix2.multiplyByVector(toDesiredInComputed, point2D, point2D); - point2D.x += boundingRectangleCenter.longitude; - point2D.y += boundingRectangleCenter.latitude; - - // Convert point into east-north texture coordinate space - point2D.x = (point2D.x - boundingRectangle.west) / boundingRectangle.width; - point2D.y = (point2D.y - boundingRectangle.south) / boundingRectangle.height; - } + var minXYCorner = Cartesian2.unpack(textureCoordinateRotationPoints, 0, points2D[0]); + var maxYCorner = Cartesian2.unpack(textureCoordinateRotationPoints, 2, points2D[1]); + var maxXCorner = Cartesian2.unpack(textureCoordinateRotationPoints, 4, points2D[2]); - // Encode these points in the batch table. - // These will be used to create lines in east-north texture coordinate space. - // Distance from an east-north texture coordinate to each line is a desired texture coordinate. - var minXYCorner = points2D[0]; - var maxYCorner = points2D[1]; - var maxXCorner = points2D[2]; attributes.uMaxVmax = new GeometryInstanceAttribute({ componentDatatype: ComponentDatatype.FLOAT, componentsPerAttribute: 4, @@ -463,6 +424,7 @@ define([ var enuMatrixScratch = new Matrix4(); var inverseEnuScratch = new Matrix4(); var rectanglePointCartesianScratch = new Cartesian3(); + var rectangleCenterScratch = new Cartographic(); var pointsCartographicScratch = [ new Cartographic(), new Cartographic(), @@ -569,17 +531,16 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound - * @param {Rectangle} unrotatedTextureRectangle TODO: doc + * @param {Rectangle} textureCoordinateRotationPoints TODO: doc * @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. - * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation * @returns {Object} An attributes dictionary containing planar texture coordinate attributes. */ - ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(boundingRectangle, unrotatedTextureRectangle, ellipsoid, projection, height, textureCoordinateRotation) { + ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, projection, height) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); - Check.typeOf.object('unrotatedTextureRectangle', unrotatedTextureRectangle); + Check.defined('textureCoordinateRotationPoints', textureCoordinateRotationPoints); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); @@ -590,7 +551,7 @@ define([ computeRectangleBounds(boundingRectangle, ellipsoid, defaultValue(height, 0.0), corner, eastward, northward); var attributes = {}; - addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation); + addTextureCoordinateRotationAttributes(attributes, textureCoordinateRotationPoints); var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch); attributes.southWest_HIGH = new GeometryInstanceAttribute({ @@ -659,16 +620,15 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound - * @param {Rectangle} unrotatedTextureRectangle TODO: doc + * @param {Rectangle} textureCoordinateRotationPoints TODO: doc * @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates * @param {MapProjection} projection The MapProjection used for 2D and Columbus View. - * @param {Number} [textureCoordinateRotation=0] Texture coordinate rotation * @returns {Object} An attributes dictionary containing spherical texture coordinate attributes. */ - ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, unrotatedTextureRectangle, ellipsoid, projection, textureCoordinateRotation) { + ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function(boundingRectangle, textureCoordinateRotationPoints, ellipsoid, projection) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('boundingRectangle', boundingRectangle); - Check.typeOf.object('unrotatedTextureRectangle', unrotatedTextureRectangle); + Check.defined('textureCoordinateRotationPoints', textureCoordinateRotationPoints); Check.typeOf.object('ellipsoid', ellipsoid); Check.typeOf.object('projection', projection); //>>includeEnd('debug'); @@ -696,7 +656,7 @@ define([ }) }; - addTextureCoordinateRotationAttributes(attributes, boundingRectangle, unrotatedTextureRectangle, textureCoordinateRotation); + addTextureCoordinateRotationAttributes(attributes, textureCoordinateRotationPoints); add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes); return attributes; }; diff --git a/Specs/Core/CircleGeometrySpec.js b/Specs/Core/CircleGeometrySpec.js index 1eefc41f0c8..1d247c4a7fd 100644 --- a/Specs/Core/CircleGeometrySpec.js +++ b/Specs/Core/CircleGeometrySpec.js @@ -164,21 +164,23 @@ defineSuite([ expect(r.west).toEqual(-1.3196344953554853); }); - it('computing unrotatedTextureRectangle property', function() { - var center = Cartesian3.fromDegrees(-75.59777, 40.03883); + it('computing textureCoordinateRotationPoints property', function() { + var center = Cartesian3.fromDegrees(0, 0); var ellipse = new CircleGeometry({ center : center, radius : 1000.0, - stRotation : CesiumMath.toRadians(45) + stRotation : CesiumMath.toRadians(90) }); - // Should be the same as rectangle property - var unrotatedTextureRectangle = ellipse.unrotatedTextureRectangle; - var rectangle = ellipse.rectangle; - expect(unrotatedTextureRectangle.north).toEqual(rectangle.north); - expect(unrotatedTextureRectangle.south).toEqual(rectangle.south); - expect(unrotatedTextureRectangle.east).toEqual(rectangle.east); - expect(unrotatedTextureRectangle.west).toEqual(rectangle.west); + // 90 degree rotation means (0, 1) should be the new min and (1, 1) (0, 0) are extents + var textureCoordinateRotationPoints = ellipse.textureCoordinateRotationPoints; + expect(textureCoordinateRotationPoints.length).toEqual(6); + expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(0, CesiumMath.EPSILON7); }); var center = Cartesian3.fromDegrees(0,0); diff --git a/Specs/Core/EllipseGeometrySpec.js b/Specs/Core/EllipseGeometrySpec.js index d1f4d0e29f0..9f331f0d484 100644 --- a/Specs/Core/EllipseGeometrySpec.js +++ b/Specs/Core/EllipseGeometrySpec.js @@ -264,20 +264,40 @@ defineSuite([ expect(r.west).toEqualEpsilon(-CesiumMath.PI, CesiumMath.EPSILON7); }); - it('computing unrotatedTextureRectangle property', function() { + it('computing textureCoordinateRotationPoints property', function() { var center = Cartesian3.fromDegrees(0, 0); var ellipse = new EllipseGeometry({ center : center, semiMajorAxis : 2000.0, semiMinorAxis : 1000.0, - stRotation : CesiumMath.toRadians(45) + stRotation : CesiumMath.toRadians(90) }); - var r = ellipse.unrotatedTextureRectangle; - expect(r.north).toEqualEpsilon(0.00024947054494468227 , CesiumMath.EPSILON7); - expect(r.south).toEqualEpsilon(-0.00024947054494468227, CesiumMath.EPSILON7); - expect(r.east).toEqualEpsilon(0.00024780049692545245, CesiumMath.EPSILON7); - expect(r.west).toEqualEpsilon(-0.00024780049692545245, CesiumMath.EPSILON7); + // 90 degree rotation means (0, 1) should be the new min and (1, 1) (0, 0) are extents + var textureCoordinateRotationPoints = ellipse.textureCoordinateRotationPoints; + expect(textureCoordinateRotationPoints.length).toEqual(6); + expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(0, CesiumMath.EPSILON7); + + ellipse = new EllipseGeometry({ + center : center, + semiMajorAxis : 2000.0, + semiMinorAxis : 1000.0, + stRotation : CesiumMath.toRadians(0) + }); + + textureCoordinateRotationPoints = ellipse.textureCoordinateRotationPoints; + expect(textureCoordinateRotationPoints.length).toEqual(6); + expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(0, CesiumMath.EPSILON7); }); var center = Cartesian3.fromDegrees(0,0); diff --git a/Specs/Core/GeometrySpec.js b/Specs/Core/GeometrySpec.js index 692b85e036f..08447225441 100644 --- a/Specs/Core/GeometrySpec.js +++ b/Specs/Core/GeometrySpec.js @@ -2,18 +2,24 @@ defineSuite([ 'Core/Geometry', 'Core/BoundingSphere', 'Core/Cartesian3', + 'Core/Math', 'Core/ComponentDatatype', + 'Core/Ellipsoid', 'Core/GeometryAttribute', 'Core/GeometryType', - 'Core/PrimitiveType' + 'Core/PrimitiveType', + 'Core/Rectangle' ], function( Geometry, BoundingSphere, Cartesian3, + CesiumMath, ComponentDatatype, + Ellipsoid, GeometryAttribute, GeometryType, - PrimitiveType) { + PrimitiveType, + Rectangle) { 'use strict'; it('constructor', function() { @@ -129,4 +135,21 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('computes textureCoordinateRotationPoints for collections of points', function() { + var positions = Cartesian3.fromDegreesArrayHeights([ + -10.0, -10.0, 0, + -10.0, 10.0, 0, + 10.0, -10.0, 0, + 10.0, 10.0, 0 + ]); + var boundingRectangle = Rectangle.fromCartesianArray(positions); + var textureCoordinateRotationPoints = Geometry._textureCoordinateRotationPoints(positions, 0.0, Ellipsoid.WGS84, boundingRectangle); + expect(textureCoordinateRotationPoints.length).toEqual(6); + expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(0, CesiumMath.EPSILON7); + }); }); diff --git a/Specs/Core/PolygonGeometrySpec.js b/Specs/Core/PolygonGeometrySpec.js index 63634cc5d4c..90d519197bb 100644 --- a/Specs/Core/PolygonGeometrySpec.js +++ b/Specs/Core/PolygonGeometrySpec.js @@ -307,7 +307,7 @@ defineSuite([ -110.0, 35.0 ]); for (i = 0; i < p.length; i++) { - expect(p[i]).toEqualEpsilon(pExpected[i], CesiumMath.EPSILON10); + expect(p[i]).toEqualEpsilon(pExpected[i], CesiumMath.EPSILON7); } var h1Expected = Cartesian3.fromDegreesArray([ @@ -317,7 +317,7 @@ defineSuite([ -122.0, 39.0 ]); for (i = 0; i < h1.length; i++) { - expect(h1[i]).toEqualEpsilon(h1Expected[i], CesiumMath.EPSILON10); + expect(h1[i]).toEqualEpsilon(h1Expected[i], CesiumMath.EPSILON7); } var h2Expected = Cartesian3.fromDegreesArray([ @@ -327,7 +327,7 @@ defineSuite([ -114.0, 36.5 ]); for (i = 0; i Date: Fri, 11 May 2018 08:12:43 -0400 Subject: [PATCH 36/40] doc tweaks --- Source/Core/CircleGeometry.js | 2 +- Source/Core/EllipseGeometry.js | 2 +- Source/Core/Geometry.js | 14 ++++++++++---- Source/Core/PolygonGeometry.js | 2 +- Source/Core/RectangleGeometry.js | 7 ++++--- Source/Shaders/ShadowVolumeAppearanceFS.glsl | 9 ++++++--- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Source/Core/CircleGeometry.js b/Source/Core/CircleGeometry.js index 457b684546d..1a6df4e04ab 100644 --- a/Source/Core/CircleGeometry.js +++ b/Source/Core/CircleGeometry.js @@ -186,7 +186,7 @@ define([ } }, /** - * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. + * For remapping texture coordinates when rendering CircleGeometries as GroundPrimitives. * @private */ textureCoordinateRotationPoints : { diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index eac049ab3cd..a65b97e4f18 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -992,7 +992,7 @@ define([ } }, /** - * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. + * For remapping texture coordinates when rendering EllipseGeometries as GroundPrimitives. * @private */ textureCoordinateRotationPoints : { diff --git a/Source/Core/Geometry.js b/Source/Core/Geometry.js index 29749d808c3..7190996c80e 100644 --- a/Source/Core/Geometry.js +++ b/Source/Core/Geometry.js @@ -228,12 +228,18 @@ define([ * For remapping texture coordinates when rendering GroundPrimitives with materials. * GroundPrimitive texture coordinates are computed to align with the cartographic coordinate system on the globe. * However, EllipseGeometry, RectangleGeometry, and PolygonGeometry all bake rotations to per-vertex texture coordinates - * and use different algorithms to do so. + * using different strategies. * * This method is used by EllipseGeometry and PolygonGeometry to approximate the same visual effect. - * We encapsulate rotation and necessary scale by computing a "transformed" texture coordinate system and compute - * a set of points from which "cartographic" texture coordinates can be remapped to the "transformed" system - * using distances to lines in 2D. + * We encapsulate rotation and scale by computing a "transformed" texture coordinate system and computing + * a set of reference points from which "cartographic" texture coordinates can be remapped to the "transformed" + * system using distances to lines in 2D. + * + * This approximation becomes less accurate as the covered area increases, especially for GroundPrimitives near the poles, + * but is generally reasonable for polygons and ellipses around the size of USA states. + * + * RectangleGeometry has its own version of this method that computes remapping coordinates using cartographic space + * as an intermediary instead of local ENU, which is more accurate for large-area rectangles. * * @param {Cartesian3[]} positions Array of positions outlining the geometry * @param {Number} stRotation Texture coordinate rotation. diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 47a3918e947..6e8aefb65ed 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -940,7 +940,7 @@ define([ } }, /** - * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. + * For remapping texture coordinates when rendering PolygonGeometries as GroundPrimitives. * @private */ textureCoordinateRotationPoints : { diff --git a/Source/Core/RectangleGeometry.js b/Source/Core/RectangleGeometry.js index 675bb978f13..0f5016d3a0c 100644 --- a/Source/Core/RectangleGeometry.js +++ b/Source/Core/RectangleGeometry.js @@ -891,7 +891,7 @@ define([ var unrotatedTextureRectangle = computeRectangle(rotatedRectangle, unrotatedTextureRectangleScratch); // Assume a computed "east-north" texture coordinate system based on spherical or planar tricks, bounded by `boundingRectangle`. - // The "desired" texture coordinate system forms an oriented rectangle (un-oriented provided) around the geometry that completely and tightly bounds it. + // The "desired" texture coordinate system forms an oriented rectangle (un-oriented computed) around the geometry that completely and tightly bounds it. // We want to map from the "east-north" texture coordinate system into the "desired" system using a pair of lines (analagous planes in 2D) // Compute 3 corners of the "desired" texture coordinate system in "east-north" texture space by the following in cartographic space: // - rotate 3 of the corners in unrotatedTextureRectangle by stRotation around the center of the bounding rectangle @@ -948,8 +948,9 @@ define([ } }, /** - * For remapping texture coordinates when rendering Ellipses as GroundPrimitives. - * This version permits skew in textures by computing offsets in cartographic space. + * For remapping texture coordinates when rendering RectangleGeometries as GroundPrimitives. + * This version permits skew in textures by computing offsets directly in cartographic space and + * more accurately approximates rendering RectangleGeometries with height as standard Primitives. * @see Geometry#_textureCoordinateRotationPoints * @private */ diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index b501adfd8c3..5fb3f7c4f04 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -109,9 +109,9 @@ void main(void) #else // PER_INSTANCE_COLOR -// Material support. -// USES_ is distinct from REQUIRES_, because some things are dependencies of each other or -// dependencies for culling but might not actually be used by the material. + // Material support. + // USES_ is distinct from REQUIRES_, because some things are dependencies of each other or + // dependencies for culling but might not actually be used by the material. czm_materialInput materialInput; @@ -128,6 +128,9 @@ void main(void) #endif #ifdef USES_ST + // Remap texture coordinates from computed (approximately aligned with cartographic space) to the desired + // texture coordinate system, which typically forms a tight oriented bounding box around the geometry. + // Shader is provided a set of reference points for remapping. materialInput.st.x = czm_lineDistance(v_uvMin, v_uMaxAndInverseDistance.xy, uv) * v_uMaxAndInverseDistance.z; materialInput.st.y = czm_lineDistance(v_uvMin, v_vMaxAndInverseDistance.xy, uv) * v_vMaxAndInverseDistance.z; #endif From 2aaf9796ae5eb68653cce54f492aaf956b808bc6 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Fri, 11 May 2018 15:55:29 -0400 Subject: [PATCH 37/40] PR feedback and cleanup --- Source/Core/CorridorGeometry.js | 12 +++++------- Source/Core/EllipseGeometry.js | 7 +++++-- Source/Core/PolygonGeometry.js | 2 +- Source/Scene/ShadowVolumeAppearance.js | 4 ++-- Specs/Core/CorridorGeometrySpec.js | 18 ++++++++++-------- ...StaticGroundGeometryPerMaterialBatchSpec.js | 10 +++++----- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Source/Core/CorridorGeometry.js b/Source/Core/CorridorGeometry.js index cd6cb7eb45e..476a1c3a305 100644 --- a/Source/Core/CorridorGeometry.js +++ b/Source/Core/CorridorGeometry.js @@ -1047,17 +1047,15 @@ define([ } }, /** - * For stRotation on GroundPrimitives. - * Returns the rectangle part of an oriented rectangle that tightly bounds - * the oriented geometry in Cartographic space. + * For remapping texture coordinates when rendering CorridorGeometries as GroundPrimitives. * - * Corridors don't support geometry orientation or stRotation, - * so just return the rectangle. + * Corridors don't support stRotation, + * so just return the corners of the original system. * @private */ - unrotatedTextureRectangle : { + textureCoordinateRotationPoints : { get : function() { - return this.rectangle; + return [0, 0, 0, 1, 1, 0]; } } }); diff --git a/Source/Core/EllipseGeometry.js b/Source/Core/EllipseGeometry.js index a65b97e4f18..246d41320cb 100644 --- a/Source/Core/EllipseGeometry.js +++ b/Source/Core/EllipseGeometry.js @@ -755,7 +755,7 @@ define([ this._workerName = 'createEllipseGeometry'; this._rectangle = undefined; - this._unrotatedTextureRectangle = undefined; + this._textureCoordinateRotationPoints = undefined; } /** @@ -997,7 +997,10 @@ define([ */ textureCoordinateRotationPoints : { get : function() { - return textureCoordinateRotationPoints(this); + if (!defined(this._textureCoordinateRotationPoints)) { + this._textureCoordinateRotationPoints = textureCoordinateRotationPoints(this); + } + return this._textureCoordinateRotationPoints; } } }); diff --git a/Source/Core/PolygonGeometry.js b/Source/Core/PolygonGeometry.js index 6e8aefb65ed..ddd97f6c05a 100644 --- a/Source/Core/PolygonGeometry.js +++ b/Source/Core/PolygonGeometry.js @@ -597,7 +597,7 @@ define([ this._workerName = 'createPolygonGeometry'; this._rectangle = undefined; - this._unrotatedTextureRectangle = undefined; + this._textureCoordinateRotationPoints = undefined; /** * The number of elements used to pack the object into an array. diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index ef602fbb573..e18161cbc25 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -531,7 +531,7 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound - * @param {Rectangle} textureCoordinateRotationPoints TODO: doc + * @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. @@ -620,7 +620,7 @@ define([ * @private * * @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound - * @param {Rectangle} textureCoordinateRotationPoints TODO: doc + * @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. diff --git a/Specs/Core/CorridorGeometrySpec.js b/Specs/Core/CorridorGeometrySpec.js index 807878f7af9..dca5468bf5b 100644 --- a/Specs/Core/CorridorGeometrySpec.js +++ b/Specs/Core/CorridorGeometrySpec.js @@ -281,7 +281,7 @@ defineSuite([ expect(CesiumMath.toDegrees(r.west)).toEqual(-67.6550047734171); }); - it('computing unrotatedTextureRectangle property', function() { + it('computing textureCoordinateRotationPoints property', function() { var c = new CorridorGeometry({ vertexFormat : VertexFormat.POSITION_ONLY, positions : Cartesian3.fromDegreesArray([ @@ -294,13 +294,15 @@ defineSuite([ granularity : Math.PI / 6.0 }); - // Corridors don't support geometry orientation or stRotation, so expect this to equal the standard rectangle. - var unrotatedTextureRectangle = c.unrotatedTextureRectangle; - var rectangle = c.rectangle; - expect(unrotatedTextureRectangle.north).toEqual(rectangle.north); - expect(unrotatedTextureRectangle.south).toEqual(rectangle.south); - expect(unrotatedTextureRectangle.east).toEqual(rectangle.east); - expect(unrotatedTextureRectangle.west).toEqual(rectangle.west); + // Corridors don't support geometry orientation or stRotation, so expect this to equal the original coordinate system. + var textureCoordinateRotationPoints = c.textureCoordinateRotationPoints; + expect(textureCoordinateRotationPoints.length).toEqual(6); + expect(textureCoordinateRotationPoints[0]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[1]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[2]).toEqualEpsilon(0, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[3]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[4]).toEqualEpsilon(1, CesiumMath.EPSILON7); + expect(textureCoordinateRotationPoints[5]).toEqualEpsilon(0, CesiumMath.EPSILON7); }); var positions = Cartesian3.fromDegreesArray([ diff --git a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js index 67797e85a23..f079d1ce26a 100644 --- a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js +++ b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js @@ -67,7 +67,7 @@ defineSuite([ }); it('handles shared material being invalidated with geometry', function() { - if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { // Don't fail if materials on GroundPrimitive not supported return; } @@ -121,7 +121,7 @@ defineSuite([ }); it('updates with sampled distance display condition out of range', function() { - if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { // Don't fail if materials on GroundPrimitive not supported return; } @@ -171,7 +171,7 @@ defineSuite([ }); it('shows only one primitive while rebuilding primitive', function() { - if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { // Don't fail if materials on GroundPrimitive not supported return; } @@ -245,7 +245,7 @@ defineSuite([ }); it('batches overlapping entities with the same material separately', function() { - if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { // Don't fail if materials on GroundPrimitive not supported return; } @@ -290,7 +290,7 @@ defineSuite([ }); it('batches nonoverlapping entities that both use color materials', function() { - if (!GroundPrimitive.isSupported(scene) | !GroundPrimitive.supportsMaterials(scene)) { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { // Don't fail if materials on GroundPrimitive not supported return; } From 16c7305ba9587e0524f6cbfe99296a2e1f1e20e8 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 15 May 2018 13:40:37 -0400 Subject: [PATCH 38/40] handle ClassificationType BOTH and CESIUM_3D_TILE with materials on GroundPrimitives by falling back to TERRAIN --- .../Ground Primitive Materials.html | 6 +- .../development/Terrain Entity Batching.html | 9 +- CHANGES.md | 5 +- Source/DataSources/GeometryVisualizer.js | 23 ++- .../StaticGroundGeometryPerMaterialBatch.js | 12 +- Source/Scene/ClassificationPrimitive.js | 5 +- Source/Scene/GroundPrimitive.js | 11 +- Specs/DataSources/GeometryVisualizerSpec.js | 165 ++++++++++++++++++ ...taticGroundGeometryPerMaterialBatchSpec.js | 12 +- Specs/Scene/GroundPrimitiveSpec.js | 85 ++++++++- 10 files changed, 298 insertions(+), 35 deletions(-) diff --git a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html index 4ebbdad5a59..53990f9fc3b 100644 --- a/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html +++ b/Apps/Sandcastle/gallery/development/Ground Primitive Materials.html @@ -444,7 +444,8 @@ }), appearance : new Cesium.EllipsoidSurfaceAppearance({ aboveGround : false - }) + }), + classificationType : Cesium.ClassificationType.TERRAIN })); worldRectangle = scene.primitives.add(new Cesium.GroundPrimitive({ @@ -457,7 +458,8 @@ appearance : new Cesium.EllipsoidSurfaceAppearance({ aboveGround : false }), - show : false + show : false, + classificationType : Cesium.ClassificationType.TERRAIN })); var initialPosition = Cesium.Cartesian3.fromRadians(-2.1344873183780484, 0.8071380277370774, 5743.394497982162); diff --git a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html index 9aaa92bc6a0..35f819dacf2 100644 --- a/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html +++ b/Apps/Sandcastle/gallery/development/Terrain Entity Batching.html @@ -78,7 +78,8 @@ rectangle : { coordinates : Cesium.Rectangle.fromDegrees(cornerLon, cornerLat, cornerLon + 0.009, cornerLat + 0.009), - material : Cesium.Color.fromRandom().withAlpha(0.5) + material : Cesium.Color.fromRandom().withAlpha(0.5), + classificationType : Cesium.ClassificationType.TERRAIN } }); } @@ -95,7 +96,8 @@ name : 'checkerboard rectangle', rectangle : { coordinates : Cesium.Rectangle.fromDegrees(-122.17778, 46.36169, -120.17778, 48.36169), - material : checkerboard + material : checkerboard, + classificationType : Cesium.ClassificationType.TERRAIN } }); @@ -107,7 +109,8 @@ semiMinorAxis : 15000.0, semiMajorAxis : 30000.0, material : '../images/Cesium_Logo_Color.jpg', - stRotation : Cesium.Math.toRadians(45) + stRotation : Cesium.Math.toRadians(45), + classificationType : Cesium.ClassificationType.TERRAIN } }); diff --git a/CHANGES.md b/CHANGES.md index 690976f74be..93a28b6bafd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,10 @@ Change Log * Removed `Scene.copyGlobeDepth`. Globe depth will now be copied by default when supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ##### Additions :tada: -* Added support for materials on terrain entities and `GroundPrimitives`. This functionality requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so it is not available in Internet Explorer. Textured materials on terrain entities and `GroundPrimitives` are best suited for notational patterns and are not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) +* Added support for materials on terrain entities (entities with unspecified `height`) and `GroundPrimitives`. +Materials on `GroundPrimitives` are only available for `ClassificationType.TERRAIN` at this time, and adding a material to a terrain `Entity` will cause it to behave as if it is `ClassificationType.TERRAIN`. +Materials on terrain entities and `GroundPrimitives` also requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so it is not available in Internet Explorer. +Textured materials on terrain entities and `GroundPrimitives` are best suited for notational patterns and are not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) * Added `GroundPrimitive.supportsMaterials` and `Entity.supportsMaterialsforEntitiesOnTerrain`, both of which can be used to check if materials on terrain entities and `GroundPrimitives` is supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ### 1.45 - 2018-05-01 diff --git a/Source/DataSources/GeometryVisualizer.js b/Source/DataSources/GeometryVisualizer.js index ca363c2404a..c1da09f0e21 100644 --- a/Source/DataSources/GeometryVisualizer.js +++ b/Source/DataSources/GeometryVisualizer.js @@ -162,28 +162,30 @@ define([ } var numberOfClassificationTypes = ClassificationType.NUMBER_OF_CLASSIFICATION_TYPES; - var groundMaterialBatches; var groundColorBatches = new Array(numberOfClassificationTypes); + var groundmaterialBatches = []; if (supportsMaterialsforEntitiesOnTerrain) { - groundMaterialBatches = new Array(numberOfClassificationTypes); - + // Culling, phong shading only supported for ClassificationType.TERRAIN at the moment because + // tileset depth information not yet available. + groundColorBatches[ClassificationType.TERRAIN] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance); for (i = 0; i < numberOfClassificationTypes; ++i) { - groundColorBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, PerInstanceColorAppearance, i); - groundMaterialBatches[i] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance, i); + if (i !== ClassificationType.TERRAIN) { + groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, i); + } } + groundmaterialBatches[0] = new StaticGroundGeometryPerMaterialBatch(groundPrimitives, MaterialAppearance); + this._groundTerrainMaterialBatch = groundmaterialBatches[0]; } else { - groundMaterialBatches = []; for (i = 0; i < numberOfClassificationTypes; ++i) { groundColorBatches[i] = new StaticGroundGeometryColorBatch(groundPrimitives, i); } } - this._groundMaterialBatches = groundMaterialBatches; this._groundColorBatches = groundColorBatches; this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives); - this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatches, this._groundMaterialBatches, this._dynamicBatch); + this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatches, groundmaterialBatches, this._dynamicBatch); this._subscriptions = new AssociativeArray(); this._updaterSets = new AssociativeArray(); @@ -399,7 +401,10 @@ define([ this._groundColorBatches[classificationType].add(time, updater); } else { // If unsupported, updater will not be on terrain. - this._groundMaterialBatches[classificationType].add(time, updater); + // If the updater has a material, ignore input ClassificationType for now and only classify terrain. + // Culling, phong shading only supported for ClassificationType.TERRAIN at the moment because + // tileset depth information not yet available. + this._groundTerrainMaterialBatch.add(time, updater); } } else if (updater.isClosed) { if (updater.fillMaterialProperty instanceof ColorMaterialProperty) { diff --git a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js index d01d64aea3a..01eee22b114 100644 --- a/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js @@ -6,6 +6,7 @@ define([ '../Core/DistanceDisplayConditionGeometryInstanceAttribute', '../Core/ShowGeometryInstanceAttribute', '../Core/RectangleCollisionChecker', + '../Scene/ClassificationType', '../Scene/GroundPrimitive', '../Scene/ShadowVolumeAppearance', './BoundingSphereState', @@ -20,6 +21,7 @@ define([ DistanceDisplayConditionGeometryInstanceAttribute, ShowGeometryInstanceAttribute, RectangleCollisionChecker, + ClassificationType, GroundPrimitive, ShadowVolumeAppearance, BoundingSphereState, @@ -32,10 +34,9 @@ define([ var defaultDistanceDisplayCondition = new DistanceDisplayCondition(); // Encapsulates a Primitive and all the entities that it represents. - function Batch(primitives, appearanceType, classificationType, materialProperty, usingSphericalTextureCoordinates) { + function Batch(primitives, appearanceType, materialProperty, usingSphericalTextureCoordinates) { this.primitives = primitives; // scene level primitive collection this.appearanceType = appearanceType; - this.classificationType = classificationType; this.materialProperty = materialProperty; this.updaters = new AssociativeArray(); this.createPrimitive = true; @@ -155,7 +156,7 @@ define([ material : this.material // translucent and closed properties overridden }), - classificationType : this.classificationType + classificationType : ClassificationType.TERRAIN }); primitives.add(primitive); @@ -278,11 +279,10 @@ define([ /** * @private */ - function StaticGroundGeometryPerMaterialBatch(primitives, appearanceType, classificationType) { + function StaticGroundGeometryPerMaterialBatch(primitives, appearanceType) { this._items = []; this._primitives = primitives; this._appearanceType = appearanceType; - this._classificationType = classificationType; } StaticGroundGeometryPerMaterialBatch.prototype.add = function(time, updater) { @@ -304,7 +304,7 @@ define([ } } // If a compatible batch wasn't found, create a new batch. - var batch = new Batch(this._primitives, this._appearanceType, this._classificationType, updater.fillMaterialProperty, usingSphericalTextureCoordinates); + var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, usingSphericalTextureCoordinates); batch.add(time, updater, geometryInstance); items.push(batch); }; diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js index d81a82fdbe4..b2c47cef68c 100644 --- a/Source/Scene/ClassificationPrimitive.js +++ b/Source/Scene/ClassificationPrimitive.js @@ -63,8 +63,9 @@ define([ * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. - * Only the {@link PerInstanceColorAppearance} is supported at this time when using ClassificationPrimitive directly. - * For full {@link Appearance} support use {@link GroundPrimitive} instead. + * Only {@link PerInstanceColorAppearance} with the same color across all instances is supported at this time when using + * ClassificationPrimitive directly. + * For full {@link Appearance} support when classifying terrain use {@link GroundPrimitive} instead. * *

*

diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 7756c786877..0971cbbb002 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -72,6 +72,9 @@ define([ * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. * + * Only {@link PerInstanceColorAppearance} with the same color across all instances is supported at this time when + * classifying {@link ClassificationType}.CESIUM_3D_TILE and {@link ClassificationType}.BOTH. + * * Support for the WEBGL_depth_texture extension is required to use GeometryInstances with different PerInstanceColors * or materials besides PerInstanceColorAppearance. * @@ -762,6 +765,12 @@ define([ return; } + //>>includeStart('debug', pragmas.debug); + if (this.classificationType !== ClassificationType.TERRAIN && !(this.appearance instanceof PerInstanceColorAppearance)) { + throw new DeveloperError('GroundPrimitives with Materials can only classify ClassificationType.TERRAIN at this time.'); + } + //>>includeEnd('debug'); + var that = this; var primitiveOptions = this._classificationPrimitiveOptions; @@ -809,7 +818,7 @@ define([ this._minHeight = this._minTerrainHeight * exaggeration; this._maxHeight = this._maxTerrainHeight * exaggeration; - var useFragmentCulling = GroundPrimitive._supportsMaterials(frameState.context); + var useFragmentCulling = GroundPrimitive._supportsMaterials(frameState.context) && this.classificationType === ClassificationType.TERRAIN; this._useFragmentCulling = useFragmentCulling; if (useFragmentCulling) { diff --git a/Specs/DataSources/GeometryVisualizerSpec.js b/Specs/DataSources/GeometryVisualizerSpec.js index 0f51eccf215..6130986fbc6 100644 --- a/Specs/DataSources/GeometryVisualizerSpec.js +++ b/Specs/DataSources/GeometryVisualizerSpec.js @@ -907,4 +907,169 @@ defineSuite([ }); }); + it('batches ground entities by identical color if ClassificationType is not TERRAIN', function() { + var entities = new EntityCollection(); + var visualizer = new GeometryVisualizer(scene, entities, scene.primitives, scene.groundPrimitives); + + var blueColor = Color.BLUE.withAlpha(0.5); + entities.add({ + position : new Cartesian3(1, 2, 3), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor, + classificationType : ClassificationType.BOTH + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : new Cartesian3(12, 34, 45), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor, + classificationType : ClassificationType.BOTH + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : new Cartesian3(123, 456, 789), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : Color.BLUE.withAlpha(0.6), + classificationType : ClassificationType.BOTH + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(2); + + entities.removeAll(); + visualizer.destroy(); + }); + }); + + it('batches ground entities classifying terrain by material if ground entity materials is supported', function() { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { + return; + } + + var entities = new EntityCollection(); + var visualizer = new GeometryVisualizer(scene, entities, scene.primitives, scene.groundPrimitives); + + var blueColor = Color.BLUE.withAlpha(0.5); + entities.add({ + position : Cartesian3.fromDegrees(1, 2), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor, + classificationType : ClassificationType.TERRAIN + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : Cartesian3.fromDegrees(12, 34), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : blueColor, + classificationType : ClassificationType.TERRAIN + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : Cartesian3.fromDegrees(45, 67), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : Color.BLUE.withAlpha(0.6), + classificationType : ClassificationType.TERRAIN + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(1); + + entities.add({ + position : Cartesian3.fromDegrees(-1, -2), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : './Data/Images/White.png', + classificationType : ClassificationType.TERRAIN + } + }); + + entities.add({ + position : Cartesian3.fromDegrees(-12, -34), + ellipse : { + semiMajorAxis : 2, + semiMinorAxis : 1, + material : './Data/Images/White.png', + classificationType : ClassificationType.BOTH // expect to render as ClassificationType.TERRAIN + } + }); + + return pollToPromise(function() { + scene.initializeFrame(); + var isUpdated = visualizer.update(time); + scene.render(time); + return isUpdated; + }); + }).then(function() { + expect(scene.groundPrimitives.length).toEqual(2); + + entities.removeAll(); + visualizer.destroy(); + }); + }); + }, 'WebGL'); diff --git a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js index f079d1ce26a..3d33aa31f12 100644 --- a/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js +++ b/Specs/DataSources/StaticGroundGeometryPerMaterialBatchSpec.js @@ -19,7 +19,6 @@ defineSuite([ 'DataSources/PolylineGeometryUpdater', 'DataSources/PolylineGraphics', 'DataSources/TimeIntervalCollectionProperty', - 'Scene/ClassificationType', 'Scene/GroundPrimitive', 'Scene/MaterialAppearance', 'Scene/PolylineColorAppearance', @@ -47,7 +46,6 @@ defineSuite([ PolylineGeometryUpdater, PolylineGraphics, TimeIntervalCollectionProperty, - ClassificationType, GroundPrimitive, MaterialAppearance, PolylineColorAppearance, @@ -72,7 +70,7 @@ defineSuite([ return; } - var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance); var ellipse = new EllipseGraphics(); ellipse.semiMajorAxis = new ConstantProperty(2); @@ -143,7 +141,7 @@ defineSuite([ } }); - var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance); var updater = new EllipseGeometryUpdater(entity, scene); batch.add(validTime, updater); @@ -176,7 +174,7 @@ defineSuite([ return; } - var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance); function buildEntity(x, y, z) { var material = new GridMaterialProperty({ @@ -250,7 +248,7 @@ defineSuite([ return; } - var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance); var ellipse = new EllipseGraphics(); ellipse.semiMajorAxis = new ConstantProperty(2); @@ -295,7 +293,7 @@ defineSuite([ return; } - var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance, ClassificationType.BOTH); + var batch = new StaticGroundGeometryPerMaterialBatch(scene.primitives, MaterialAppearance); var entity = new Entity({ position : new Cartesian3(1234, 5678, 9101112), ellipse : { diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index e6a7d2c8f64..c1d4b9efafd 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -13,6 +13,7 @@ defineSuite([ 'Core/RectangleGeometry', 'Core/ShowGeometryInstanceAttribute', 'Renderer/Pass', + 'Scene/ClassificationType', 'Scene/EllipsoidSurfaceAppearance', 'Scene/InvertClassification', 'Scene/Material', @@ -36,6 +37,7 @@ defineSuite([ RectangleGeometry, ShowGeometryInstanceAttribute, Pass, + ClassificationType, EllipsoidSurfaceAppearance, InvertClassification, Material, @@ -463,7 +465,7 @@ defineSuite([ verifyLargerScene(batchedPrimitive, [0, 255, 255, 255], rectangle); }); - it('renders small GeometryInstances with texture', function() { + it('renders small GeometryInstances with texture classifying terrain', function() { if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { return; } @@ -488,13 +490,14 @@ defineSuite([ flat : true, material : whiteImageMaterial }), - asynchronous : false + asynchronous : false, + classificationType : ClassificationType.TERRAIN }); verifyLargerScene(smallRectanglePrimitive, [255, 255, 255, 255], smallRectangle); }); - it('renders large GeometryInstances with texture', function() { + it('renders large GeometryInstances with texture classifying terrain', function() { if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { return; } @@ -519,7 +522,8 @@ defineSuite([ flat : true, material : whiteImageMaterial }), - asynchronous : false + asynchronous : false, + classificationType : ClassificationType.TERRAIN }); verifyLargerScene(largeRectanglePrimitive, [255, 255, 255, 255], largeRectangle); @@ -1011,6 +1015,79 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('update throws when batched instance colors are different and ClassificationType is not TERRAIN', function() { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { + return; + } + + var rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 1.0, 1.0)); + var rectangleInstance1 = new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : new Rectangle(rectangle.west, rectangle.south, rectangle.east, (rectangle.north + rectangle.south) * 0.5) + }), + id : 'rectangle1', + attributes : { + color : rectColorAttribute + } + }); + rectColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); + var rectangleInstance2 = new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : new Rectangle(rectangle.west, (rectangle.north + rectangle.south) * 0.5, rectangle.east, rectangle.north) + }), + id : 'rectangle2', + attributes : { + color : rectColorAttribute + } + }); + + primitive = new GroundPrimitive({ + geometryInstances : [rectangleInstance1, rectangleInstance2], + asynchronous : false, + classificationType : ClassificationType.BOTH + }); + + expect(function() { + verifyGroundPrimitiveRender(primitive, rectColorAttribute.value); + }).toThrowDeveloperError(); + }); + + it('update throws with texture and ClassificationType that is not TERRAIN', function() { + if (!GroundPrimitive.isSupported(scene) || !GroundPrimitive.supportsMaterials(scene)) { + return; + } + + var whiteImageMaterial = Material.fromType(Material.DiffuseMapType); + whiteImageMaterial.uniforms.image = './Data/Images/White.png'; + + var radians = CesiumMath.toRadians(0.1); + var west = rectangle.west; + var south = rectangle.south; + var smallRectangle = new Rectangle(west, south, west + radians, south + radians); + var smallRectanglePrimitive = new GroundPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : smallRectangle + }), + id : 'smallRectangle' + }), + appearance : new EllipsoidSurfaceAppearance({ + aboveGround : false, + flat : true, + material : whiteImageMaterial + }), + asynchronous : false, + classificationType : ClassificationType.BOTH + }); + + expect(function() { + verifyLargerScene(smallRectanglePrimitive, [255, 255, 255, 255], smallRectangle); + }).toThrowDeveloperError(); + }); + it('update throws when one batched instance color is undefined', function() { if (!GroundPrimitive.isSupported(scene)) { return; From fbf05c85855752c4bd6a81cb910b7bcc59683c4b Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 15 May 2018 14:41:35 -0400 Subject: [PATCH 39/40] format CHANGES.md entry with bullets --- CHANGES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 93a28b6bafd..a151c4bfc60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,10 +11,10 @@ Change Log * Removed `Scene.copyGlobeDepth`. Globe depth will now be copied by default when supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ##### Additions :tada: -* Added support for materials on terrain entities (entities with unspecified `height`) and `GroundPrimitives`. -Materials on `GroundPrimitives` are only available for `ClassificationType.TERRAIN` at this time, and adding a material to a terrain `Entity` will cause it to behave as if it is `ClassificationType.TERRAIN`. -Materials on terrain entities and `GroundPrimitives` also requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so it is not available in Internet Explorer. -Textured materials on terrain entities and `GroundPrimitives` are best suited for notational patterns and are not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) +* Added support for materials on terrain entities (entities with unspecified `height`) and `GroundPrimitives`. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) + * Only available for `ClassificationType.TERRAIN` at this time. Adding a material to a terrain `Entity` will cause it to behave as if it is `ClassificationType.TERRAIN`. + * Requires depth texture support (`WEBGL_depth_texture` or `WEBKIT_WEBGL_depth_texture`), so materials on terrain entities and `GroundPrimitives` are not supported in Internet Explorer. + * Best suited for notational patterns and not intended for precisely mapping textures to terrain - for that use case, use `SingleTileImageryProvider`. * Added `GroundPrimitive.supportsMaterials` and `Entity.supportsMaterialsforEntitiesOnTerrain`, both of which can be used to check if materials on terrain entities and `GroundPrimitives` is supported. [#6393](https://github.com/AnalyticalGraphicsInc/cesium/pull/6393) ### 1.45 - 2018-05-01 From 4f580beecf6bbd9ed68889e294833a032680d23d Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 15 May 2018 14:51:33 -0400 Subject: [PATCH 40/40] add textured terrain entity example to Ground Clamping Sandcastle --- Apps/Sandcastle/gallery/Ground Clamping.html | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Apps/Sandcastle/gallery/Ground Clamping.html b/Apps/Sandcastle/gallery/Ground Clamping.html index f8ee54a9b1d..bba58ebb09e 100644 --- a/Apps/Sandcastle/gallery/Ground Clamping.html +++ b/Apps/Sandcastle/gallery/Ground Clamping.html @@ -121,6 +121,30 @@ viewer.zoomTo(e); } }, { + text : 'Draw Textured Polygon', + onselect : function() { + if (!Cesium.Entity.supportsMaterialsforEntitiesOnTerrain(viewer.scene)) { + window.alert('Terrain Entity materials are not supported on this platform'); + return; + } + + var e = viewer.entities.add({ + polygon : { + hierarchy : { + positions : [new Cesium.Cartesian3(-2358138.847340281, -3744072.459541374, 4581158.5714175375), + new Cesium.Cartesian3(-2357231.4925370603, -3745103.7886602185, 4580702.9757762635), + new Cesium.Cartesian3(-2355912.902205431, -3744249.029778454, 4582402.154378103), + new Cesium.Cartesian3(-2357208.0209552636, -3743553.4420488174, 4581961.863286629)] + }, + material : '../images/Cesium_Logo_Color.jpg', + classificationType : Cesium.ClassificationType.TERRAIN, + stRotation : Cesium.Math.toRadians(45) + } + }); + + viewer.zoomTo(e); + } +}, { text : 'Draw Rectangle', onselect : function() { var e = viewer.entities.add({