diff --git a/Apps/Sandcastle/gallery/Classification.html b/Apps/Sandcastle/gallery/Classification.html new file mode 100644 index 000000000000..bafb8cf32ffa --- /dev/null +++ b/Apps/Sandcastle/gallery/Classification.html @@ -0,0 +1,232 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Classification.jpg b/Apps/Sandcastle/gallery/Classification.jpg new file mode 100644 index 000000000000..cea1adae8f22 Binary files /dev/null and b/Apps/Sandcastle/gallery/Classification.jpg differ diff --git a/CHANGES.md b/CHANGES.md index c02e3b4b552e..8dc9ee882d44 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ Change Log * Updated `Billboard`, `Label` and `PointPrimitive` constructors to clone `NearFarScale` parameters [#5654](https://github.com/AnalyticalGraphicsInc/cesium/pull/5654) * Added `FrustumGeometry` and `FrustumOutlineGeometry`. [#5649](https://github.com/AnalyticalGraphicsInc/cesium/pull/5649) * Added an `options` parameter to the constructors of `PerspectiveFrustum`, `PerspectiveOffCenterFrustum`, `OrthographicFrustum`, and `OrthographicOffCenterFrustum` to set properties. [#5649](https://github.com/AnalyticalGraphicsInc/cesium/pull/5649) +* Added `ClassificationPrimitive` which defines a volume and draws the intersection of the volume and terrain or 3D Tiles. [#5625](https://github.com/AnalyticalGraphicsInc/cesium/pull/5625) ### 1.35.2 - 2017-07-11 diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index fd2a50198a5f..0bcd5f7e3181 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -13,6 +13,7 @@ define([ '../Core/getStringFromTypedArray', '../Core/RequestType', '../Core/RuntimeError', + '../Renderer/Pass', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', @@ -33,6 +34,7 @@ define([ getStringFromTypedArray, RequestType, RuntimeError, + Pass, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, @@ -352,6 +354,7 @@ define([ gltf : gltfView, cull : false, // The model is already culled by 3D Tiles releaseGltfJson : true, // Models are unique and will not benefit from caching so save memory + opaquePass : Pass.CESIUM_3D_TILE, // Draw opaque portions of the model during the 3D Tiles pass basePath : basePath, requestType : RequestType.TILES3D, modelMatrix : tile.computedTransform, diff --git a/Source/Scene/Cesium3DTileBatchTable.js b/Source/Scene/Cesium3DTileBatchTable.js index 79401ae40314..7526fe2cbdf0 100644 --- a/Source/Scene/Cesium3DTileBatchTable.js +++ b/Source/Scene/Cesium3DTileBatchTable.js @@ -1305,10 +1305,6 @@ define([ // even though their style is opaque. var translucentCommand = (derivedCommand.pass === Pass.TRANSLUCENT); - if (!translucentCommand) { - derivedCommand.pass = Pass.CESIUM_3D_TILE; - } - derivedCommand.uniformMap = defined(derivedCommand.uniformMap) ? derivedCommand.uniformMap : {}; derivedCommand.uniformMap.tile_translucentCommand = function() { return translucentCommand; diff --git a/Source/Scene/ClassificationPrimitive.js b/Source/Scene/ClassificationPrimitive.js new file mode 100644 index 000000000000..86440fcecf86 --- /dev/null +++ b/Source/Scene/ClassificationPrimitive.js @@ -0,0 +1,918 @@ +/*global define*/ +define([ + '../Core/ColorGeometryInstanceAttribute', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/GeometryInstance', + '../Core/isArray', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Shaders/ShadowVolumeFS', + '../Shaders/ShadowVolumeVS', + '../ThirdParty/when', + './BlendingState', + './DepthFunction', + './PerInstanceColorAppearance', + './Primitive', + './SceneMode', + './StencilFunction', + './StencilOperation' + ], function( + ColorGeometryInstanceAttribute, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + GeometryInstance, + isArray, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + ShadowVolumeFS, + ShadowVolumeVS, + when, + BlendingState, + DepthFunction, + PerInstanceColorAppearance, + Primitive, + SceneMode, + StencilFunction, + StencilOperation) { + 'use strict'; + + 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 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. + *

+ *

+ * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there + * will be rendering artifacts for some viewing angles. + *

+ *

+ * Valid geometries are {@link BoxGeometry}, {@link CylinderGeometry}, {@link EllipsoidGeometry}, {@link PolylineVolumeGeometry}, and {@link SphereGeometry}. + *

+ *

+ * Geometries that follow the surface of the ellipsoid, such as {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}, + * are also valid if they are extruded volumes; otherwise, they will not be rendered. + *

+ * + * @alias ClassificationPrimitive + * @constructor + * + * @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 {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. + * @param {Boolean} [options.compressVertices=true] When true, the geometry vertices are compressed, which will save memory. + * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first. + * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. + * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true on + * creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false. + * + * @see Primitive + * @see GroundPrimitive + * @see GeometryInstance + * @see Appearance + */ + function ClassificationPrimitive(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The geometry instance rendered with this primitive. This may + * be undefined if options.releaseGeometryInstances + * is true when the primitive is constructed. + *

+ * Changing this property after the primitive is rendered has no effect. + *

+ *

+ * Because of the rendering technique used, all geometry instances must be the same color. + * If there is an instance with a differing color, a DeveloperError will be thrown + * on the first attempt to render. + *

+ * + * @readonly + * @type {Array|GeometryInstance} + * + * @default undefined + */ + this.geometryInstances = options.geometryInstances; + /** + * Determines if the primitive will be shown. This affects all geometry + * instances in the primitive. + * + * @type {Boolean} + * + * @default true + */ + this.show = defaultValue(options.show, true); + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the bounding sphere for each draw command in the primitive. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + /** + * This property is for debugging only; it is not for production use nor is it optimized. + *

+ * Draws the shadow volume for each geometry in the primitive. + *

+ * + * @type {Boolean} + * + * @default false + */ + this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); + this._debugShowShadowVolume = false; + + // These are used by GroundPrimitive to augment the shader and uniform map. + this._extruded = defaultValue(options._extruded, false); + this._uniformMap = options._uniformMap; + + this._sp = undefined; + this._spPick = undefined; + + this._rsStencilPreloadPass = undefined; + this._rsStencilDepthPass = undefined; + this._rsColorPass = undefined; + this._rsPickPass = undefined; + + this._ready = false; + this._readyPromise = when.defer(); + + this._primitive = undefined; + this._pickPrimitive = options._pickPrimitive; + + var appearance = new PerInstanceColorAppearance({ + flat : true + }); + + var readOnlyAttributes; + if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { + readOnlyAttributes = ClassificationPrimitiveReadOnlyInstanceAttributes; + } + + this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; + this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; + + this._primitiveOptions = { + geometryInstances : undefined, + appearance : appearance, + vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), + interleave : defaultValue(options.interleave, false), + releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), + allowPicking : defaultValue(options.allowPicking, true), + asynchronous : defaultValue(options.asynchronous, true), + compressVertices : defaultValue(options.compressVertices, true), + _readOnlyInstanceAttributes : readOnlyAttributes, + _createBoundingVolumeFunction : undefined, + _createRenderStatesFunction : undefined, + _createShaderProgramFunction : undefined, + _createCommandsFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _createPickOffsets : true + }; + } + + defineProperties(ClassificationPrimitive.prototype, { + /** + * When true, geometry vertices are optimized for the pre and post-vertex-shader caches. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + vertexCacheOptimize : { + get : function() { + return this._primitiveOptions.vertexCacheOptimize; + } + }, + + /** + * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default false + */ + interleave : { + get : function() { + return this._primitiveOptions.interleave; + } + }, + + /** + * When true, the primitive does not keep a reference to the input geometryInstances to save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + releaseGeometryInstances : { + get : function() { + return this._primitiveOptions.releaseGeometryInstances; + } + }, + + /** + * When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + allowPicking : { + get : function() { + return this._primitiveOptions.allowPicking; + } + }, + + /** + * Determines if the geometry instances will be created and batched on a web worker. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + asynchronous : { + get : function() { + return this._primitiveOptions.asynchronous; + } + }, + + /** + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + compressVertices : { + get : function() { + return this._primitiveOptions.compressVertices; + } + }, + + /** + * Determines if the primitive is complete and ready to render. If this property is + * true, the primitive will be rendered the next time that {@link ClassificationPrimitive#update} + * is called. + * + * @memberof ClassificationPrimitive.prototype + * + * @type {Boolean} + * @readonly + */ + ready : { + get : function() { + return this._ready; + } + }, + + /** + * Gets a promise that resolves when the primitive is ready to render. + * @memberof ClassificationPrimitive.prototype + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } + } + }); + + /** + * Determines if ClassificationPrimitive rendering is supported. + * + * @param {Scene} scene The scene. + * @returns {Boolean} true if ClassificationPrimitives are supported; otherwise, returns false + */ + ClassificationPrimitive.isSupported = function(scene) { + return scene.context.stencilBuffer; + }; + + function getStencilPreloadRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.ALWAYS, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.DECREMENT_WRAP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.ALWAYS, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.INCREMENT_WRAP, + zPass : StencilOperation.INCREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false + }; + } + + function getStencilDepthRenderState(enableStencil) { + return { + colorMask : { + red : false, + green : false, + blue : false, + alpha : false + }, + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.ALWAYS, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.INCREMENT_WRAP + }, + backFunction : StencilFunction.ALWAYS, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : true, + func : DepthFunction.LESS_OR_EQUAL + }, + depthMask : false + }; + } + + + function getColorRenderState(enableStencil) { + return { + stencilTest : { + enabled : enableStencil, + frontFunction : StencilFunction.NOT_EQUAL, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.NOT_EQUAL, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }; + } + + var pickRenderState = { + stencilTest : { + enabled : true, + frontFunction : StencilFunction.NOT_EQUAL, + frontOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + backFunction : StencilFunction.NOT_EQUAL, + backOperation : { + fail : StencilOperation.KEEP, + zFail : StencilOperation.KEEP, + zPass : StencilOperation.DECREMENT_WRAP + }, + reference : 0, + mask : ~0 + }, + depthTest : { + enabled : false + }, + depthMask : false + }; + + function createRenderStates(classificationPrimitive, context, appearance, twoPasses) { + if (defined(classificationPrimitive._rsStencilPreloadPass)) { + return; + } + var stencilEnabled = !classificationPrimitive.debugShowShadowVolume; + + classificationPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); + classificationPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); + classificationPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); + classificationPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); + } + + function modifyForEncodedNormals(primitive, vertexShaderSource) { + if (!primitive.compressVertices) { + return vertexShaderSource; + } + + if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { + var attributeName = 'compressedAttributes'; + + //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes + var attributeDecl = 'attribute vec2 ' + attributeName + ';'; + + var globalDecl = 'vec3 extrudeDirection;\n'; + var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, ''); + modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); + var compressedMain = + 'void main() \n' + + '{ \n' + + decode + + ' czm_non_compressed_main(); \n' + + '}'; + + return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); + } + } + + function createShaderProgram(classificationPrimitive, frameState, appearance) { + if (defined(classificationPrimitive._sp)) { + return; + } + + var context = frameState.context; + var primitive = classificationPrimitive._primitive; + var vs = ShadowVolumeVS; + vs = classificationPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); + vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); + vs = Primitive._modifyShaderPosition(classificationPrimitive, vs, frameState.scene3DOnly); + vs = Primitive._updateColorAttribute(primitive, vs); + + if (classificationPrimitive._extruded) { + vs = modifyForEncodedNormals(primitive, vs); + } + + var extrudedDefine = classificationPrimitive._extruded ? 'EXTRUDED_GEOMETRY' : ''; + + var vsSource = new ShaderSource({ + defines : [extrudedDefine], + sources : [vs] + }); + var fsSource = new ShaderSource({ + sources : [ShadowVolumeFS] + }); + var attributeLocations = classificationPrimitive._primitive._attributeLocations; + + classificationPrimitive._sp = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._sp, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); + + if (classificationPrimitive._primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + + var pickVS = new ShaderSource({ + defines : [extrudedDefine], + sources : [vsPick] + }); + + var pickFS = new ShaderSource({ + sources : [ShadowVolumeFS], + pickColorQualifier : 'varying' + }); + + classificationPrimitive._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : classificationPrimitive._spPick, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); + } else { + classificationPrimitive._spPick = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : vsSource, + fragmentShaderSource : fsSource, + attributeLocations : attributeLocations + }); + } + } + + function createColorCommands(classificationPrimitive, colorCommands) { + var primitive = classificationPrimitive._primitive; + var length = primitive._va.length * 3; + colorCommands.length = length; + + var vaIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + + for (var i = 0; i < length; i += 3) { + var vertexArray = primitive._va[vaIndex++]; + + // stencil preload command + var command = colorCommands[i]; + if (!defined(command)) { + command = colorCommands[i] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // stencil depth command + command = colorCommands[i + 1]; + if (!defined(command)) { + command = colorCommands[i + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // color command + command = colorCommands[i + 2]; + if (!defined(command)) { + command = colorCommands[i + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.renderState = classificationPrimitive._rsColorPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + } + } + + function createPickCommands(classificationPrimitive, pickCommands) { + var primitive = classificationPrimitive._primitive; + var pickOffsets = primitive._pickOffsets; + var length = pickOffsets.length * 3; + pickCommands.length = length; + + var pickIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(classificationPrimitive._uniformMap); + + for (var j = 0; j < length; j += 3) { + var pickOffset = pickOffsets[pickIndex++]; + + var offset = pickOffset.offset; + var count = pickOffset.count; + var vertexArray = primitive._va[pickOffset.index]; + + // stencil preload command + var command = pickCommands[j]; + if (!defined(command)) { + command = pickCommands[j] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilPreloadPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // stencil depth command + command = pickCommands[j + 1]; + if (!defined(command)) { + command = pickCommands[j + 1] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsStencilDepthPass; + command.shaderProgram = classificationPrimitive._sp; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + + // color command + command = pickCommands[j + 2]; + if (!defined(command)) { + command = pickCommands[j + 2] = new DrawCommand({ + owner : classificationPrimitive, + primitiveType : primitive._primitiveType + }); + } + + command.vertexArray = vertexArray; + command.offset = offset; + command.count = count; + command.renderState = classificationPrimitive._rsPickPass; + command.shaderProgram = classificationPrimitive._spPick; + command.uniformMap = uniformMap; + command.pass = Pass.GROUND; + } + } + + function createCommands(classificationPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createColorCommands(classificationPrimitive, colorCommands); + createPickCommands(classificationPrimitive, pickCommands); + } + + function updateAndQueueCommands(classificationPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + var primitive = classificationPrimitive._primitive; + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); + + var boundingVolumes; + if (frameState.mode === SceneMode.SCENE3D) { + boundingVolumes = primitive._boundingSphereWC; + } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) { + boundingVolumes = primitive._boundingSphereCV; + } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) { + boundingVolumes = primitive._boundingSphere2D; + } else if (defined(primitive._boundingSphereMorph)) { + boundingVolumes = primitive._boundingSphereMorph; + } + + var commandList = frameState.commandList; + var passes = frameState.passes; + if (passes.render) { + var colorLength = colorCommands.length; + for (var i = 0; i < colorLength; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[Math.floor(i / 3)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); + } + } + + if (passes.pick) { + var pickOffsets = primitive._pickOffsets; + var length = pickOffsets.length * 3; + pickCommands.length = length; + + for (var j = 0; j < length; ++j) { + var pickOffset = pickOffsets[Math.floor(j / 3)]; + var pickCommand = pickCommands[j]; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = boundingVolumes[pickOffset.index]; + pickCommand.cull = cull; + + commandList.push(pickCommand); + } + } + } + + /** + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ * + * @exception {DeveloperError} All instance geometries must have the same primitiveType. + * @exception {DeveloperError} Appearance and material have a uniform with the same name. + * @exception {DeveloperError} Not all of the geometry instances have the same color attribute. + */ + ClassificationPrimitive.prototype.update = function(frameState) { + if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) { + return; + } + + var that = this; + var primitiveOptions = this._primitiveOptions; + + if (!defined(this._primitive)) { + var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances]; + var length = instances.length; + + var i; + var instance; + //>>includeStart('debug', pragmas.debug); + var color; + 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; + } + } + //>>includeEnd('debug'); + + var geometryInstances = new Array(length); + for (i = 0; i < length; ++i) { + instance = instances[i]; + geometryInstances[i] = new GeometryInstance({ + geometry : instance.geometry, + attributes : instance.attributes, + modelMatrix : instance.modelMatrix, + id : instance.id, + pickPrimitive : defaultValue(this._pickPrimitive, that) + }); + } + + primitiveOptions.geometryInstances = geometryInstances; + + if (defined(this._createBoundingVolumeFunction)) { + primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { + that._createBoundingVolumeFunction(frameState, geometry); + }; + } + + primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) { + createRenderStates(that, context); + }; + primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { + createShaderProgram(that, frameState); + }; + primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { + createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands); + }; + + if (defined(this._updateAndQueueCommandsFunction)) { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + that._updateAndQueueCommandsFunction(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } else { + primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); + }; + } + + this._primitive = new Primitive(primitiveOptions); + this._primitive.readyPromise.then(function(primitive) { + that._ready = true; + + if (that.releaseGeometryInstances) { + that.geometryInstances = undefined; + } + + var error = primitive._error; + if (!defined(error)) { + that._readyPromise.resolve(that); + } else { + that._readyPromise.reject(error); + } + }); + } + + if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { + this._debugShowShadowVolume = true; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); + } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { + this._debugShowShadowVolume = false; + this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); + this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); + this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); + } + + this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; + this._primitive.update(frameState); + }; + + /** + * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. + * + * @param {Object} id The id of the {@link GeometryInstance}. + * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id. + * + * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes. + * + * @example + * var attributes = primitive.getGeometryInstanceAttributes('an id'); + * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); + * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true); + */ + ClassificationPrimitive.prototype.getGeometryInstanceAttributes = function(id) { + //>>includeStart('debug', pragmas.debug); + if (!defined(this._primitive)) { + throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); + } + //>>includeEnd('debug'); + return this._primitive.getGeometryInstanceAttributes(id); + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + *

+ * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see ClassificationPrimitive#destroy + */ + ClassificationPrimitive.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + *

+ * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * e = e && e.destroy(); + * + * @see ClassificationPrimitive#isDestroyed + */ + ClassificationPrimitive.prototype.destroy = function() { + this._primitive = this._primitive && this._primitive.destroy(); + this._sp = this._sp && this._sp.destroy(); + this._spPick = this._spPick && this._spPick.destroy(); + return destroyObject(this); + }; + + return ClassificationPrimitive; +}); diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index 6deff85dbfad..9a46f35d16c4 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -4,7 +4,6 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', - '../Core/ColorGeometryInstanceAttribute', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', @@ -17,28 +16,15 @@ define([ '../Core/Math', '../Core/OrientedBoundingBox', '../Core/Rectangle', - '../Renderer/DrawCommand', - '../Renderer/Pass', - '../Renderer/RenderState', - '../Renderer/ShaderProgram', - '../Renderer/ShaderSource', - '../Shaders/ShadowVolumeFS', - '../Shaders/ShadowVolumeVS', '../ThirdParty/when', - './BlendingState', - './DepthFunction', - './PerInstanceColorAppearance', - './Primitive', - './SceneMode', - './StencilFunction', - './StencilOperation' + './ClassificationPrimitive', + './SceneMode' ], function( BoundingSphere, buildModuleUrl, Cartesian2, Cartesian3, Cartographic, - ColorGeometryInstanceAttribute, defaultValue, defined, defineProperties, @@ -51,23 +37,17 @@ define([ CesiumMath, OrientedBoundingBox, Rectangle, - DrawCommand, - Pass, - RenderState, - ShaderProgram, - ShaderSource, - ShadowVolumeFS, - ShadowVolumeVS, when, - BlendingState, - DepthFunction, - PerInstanceColorAppearance, - Primitive, - SceneMode, - StencilFunction, - StencilOperation) { + ClassificationPrimitive, + 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. @@ -144,6 +124,7 @@ define([ * })); * * @see Primitive + * @see ClassificationPrimitive * @see GeometryInstance * @see Appearance */ @@ -163,6 +144,7 @@ define([ * on the first attempt to render. *

* + * @readonly * @type {Array|GeometryInstance} * * @default undefined @@ -200,21 +182,6 @@ define([ * @default false */ this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false); - this._debugShowShadowVolume = false; - - this._sp = undefined; - this._spPick = undefined; - - this._rsStencilPreloadPass = undefined; - this._rsStencilDepthPass = undefined; - this._rsColorPass = undefined; - this._rsPickPass = undefined; - - this._uniformMap = { - u_globeMinimumAltitude: function() { - return 55000.0; - } - }; this._boundingVolumes = []; this._boundingVolumes2D = []; @@ -233,31 +200,20 @@ define([ this._boundingSpheresKeys = []; this._boundingSpheres = []; - var appearance = new PerInstanceColorAppearance({ - flat : true - }); - - var readOnlyAttributes; - var readOnlyInstanceAttributesScratch = ['color']; - - if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) { - readOnlyAttributes = readOnlyInstanceAttributesScratch; - } - + var that = this; this._primitiveOptions = { geometryInstances : undefined, - appearance : appearance, vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false), interleave : defaultValue(options.interleave, false), releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true), allowPicking : defaultValue(options.allowPicking, true), asynchronous : defaultValue(options.asynchronous, true), compressVertices : defaultValue(options.compressVertices, true), - _readOnlyInstanceAttributes : readOnlyAttributes, - _createRenderStatesFunction : undefined, - _createShaderProgramFunction : undefined, - _createCommandsFunction : undefined, - _createPickOffsets : true + _createBoundingVolumeFunction : undefined, + _updateAndQueueCommandsFunction : undefined, + _pickPrimitive : that, + _extruded : true, + _uniformMap : GroundPrimitiveUniformMap }; } @@ -393,9 +349,7 @@ define([ * @param {Scene} scene The scene. * @returns {Boolean} true if GroundPrimitives are supported; otherwise, returns false */ - GroundPrimitive.isSupported = function(scene) { - return scene.context.stencilBuffer; - }; + GroundPrimitive.isSupported = ClassificationPrimitive.isSupported; GroundPrimitive._defaultMaxTerrainHeight = 9000.0; GroundPrimitive._defaultMinTerrainHeight = -100000.0; @@ -417,123 +371,6 @@ define([ }; } - function getStencilPreloadRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.ALWAYS, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.DECREMENT_WRAP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.ALWAYS, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.INCREMENT_WRAP, - zPass : StencilOperation.INCREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false - }; - } - - function getStencilDepthRenderState(enableStencil) { - return { - colorMask : { - red : false, - green : false, - blue : false, - alpha : false - }, - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.ALWAYS, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.INCREMENT_WRAP - }, - backFunction : StencilFunction.ALWAYS, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : true, - func : DepthFunction.LESS_OR_EQUAL - }, - depthMask : false - }; - } - - - function getColorRenderState(enableStencil) { - return { - stencilTest : { - enabled : enableStencil, - frontFunction : StencilFunction.NOT_EQUAL, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.NOT_EQUAL, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false, - blending : BlendingState.ALPHA_BLEND - }; - } - - var pickRenderState = { - stencilTest : { - enabled : true, - frontFunction : StencilFunction.NOT_EQUAL, - frontOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - backFunction : StencilFunction.NOT_EQUAL, - backOperation : { - fail : StencilOperation.KEEP, - zFail : StencilOperation.KEEP, - zPass : StencilOperation.DECREMENT_WRAP - }, - reference : 0, - mask : ~0 - }, - depthTest : { - enabled : false - }, - depthMask : false - }; - var scratchBVCartesianHigh = new Cartesian3(); var scratchBVCartesianLow = new Cartesian3(); var scratchBVCartesian = new Cartesian3(); @@ -717,229 +554,6 @@ define([ } } - function createRenderStates(groundPrimitive, context, appearance, twoPasses) { - if (defined(groundPrimitive._rsStencilPreloadPass)) { - return; - } - var stencilEnabled = !groundPrimitive.debugShowShadowVolume; - - groundPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled)); - groundPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled)); - groundPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled)); - groundPrimitive._rsPickPass = RenderState.fromCache(pickRenderState); - } - - function modifyForEncodedNormals(primitive, vertexShaderSource) { - if (!primitive.compressVertices) { - return vertexShaderSource; - } - - if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) { - var attributeName = 'compressedAttributes'; - - //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes - var attributeDecl = 'attribute vec2 ' + attributeName + ';'; - - var globalDecl = 'vec3 extrudeDirection;\n'; - var decode = ' extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n'; - - var modifiedVS = vertexShaderSource; - modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, ''); - modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main'); - var compressedMain = - 'void main() \n' + - '{ \n' + - decode + - ' czm_non_compressed_main(); \n' + - '}'; - - return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n'); - } - } - - function createShaderProgram(groundPrimitive, frameState, appearance) { - if (defined(groundPrimitive._sp)) { - return; - } - - var context = frameState.context; - var primitive = groundPrimitive._primitive; - var vs = ShadowVolumeVS; - vs = groundPrimitive._primitive._batchTable.getVertexShaderCallback()(vs); - vs = Primitive._appendShowToShader(primitive, vs); - vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs); - vs = Primitive._modifyShaderPosition(groundPrimitive, vs, frameState.scene3DOnly); - vs = Primitive._updateColorAttribute(primitive, vs); - vs = modifyForEncodedNormals(primitive, vs); - - var fs = ShadowVolumeFS; - var attributeLocations = groundPrimitive._primitive._attributeLocations; - - groundPrimitive._sp = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._sp, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - - if (groundPrimitive._primitive.allowPicking) { - var vsPick = ShaderSource.createPickVertexShaderSource(vs); - vsPick = Primitive._updatePickColorAttribute(vsPick); - - var pickFS = new ShaderSource({ - sources : [fs], - pickColorQualifier : 'varying' - }); - groundPrimitive._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : groundPrimitive._spPick, - vertexShaderSource : vsPick, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); - } else { - groundPrimitive._spPick = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - } - } - - function createColorCommands(groundPrimitive, colorCommands) { - var primitive = groundPrimitive._primitive; - var length = primitive._va.length * 3; - colorCommands.length = length; - - var vaIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); - - for (var i = 0; i < length; i += 3) { - var vertexArray = primitive._va[vaIndex++]; - - // stencil preload command - var command = colorCommands[i]; - if (!defined(command)) { - command = colorCommands[i] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // stencil depth command - command = colorCommands[i + 1]; - if (!defined(command)) { - command = colorCommands[i + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // color command - command = colorCommands[i + 2]; - if (!defined(command)) { - command = colorCommands[i + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.renderState = groundPrimitive._rsColorPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - } - } - - function createPickCommands(groundPrimitive, pickCommands) { - var primitive = groundPrimitive._primitive; - var pickOffsets = primitive._pickOffsets; - var length = pickOffsets.length * 3; - pickCommands.length = length; - - var pickIndex = 0; - var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); - - for (var j = 0; j < length; j += 3) { - var pickOffset = pickOffsets[pickIndex++]; - - var offset = pickOffset.offset; - var count = pickOffset.count; - var vertexArray = primitive._va[pickOffset.index]; - - // stencil preload command - var command = pickCommands[j]; - if (!defined(command)) { - command = pickCommands[j] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilPreloadPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // stencil depth command - command = pickCommands[j + 1]; - if (!defined(command)) { - command = pickCommands[j + 1] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsStencilDepthPass; - command.shaderProgram = groundPrimitive._sp; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - - // color command - command = pickCommands[j + 2]; - if (!defined(command)) { - command = pickCommands[j + 2] = new DrawCommand({ - owner : groundPrimitive, - primitiveType : primitive._primitiveType - }); - } - - command.vertexArray = vertexArray; - command.offset = offset; - command.count = count; - command.renderState = groundPrimitive._rsPickPass; - command.shaderProgram = groundPrimitive._spPick; - command.uniformMap = uniformMap; - command.pass = Pass.GROUND; - } - } - - function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { - createColorCommands(groundPrimitive, colorCommands); - createPickCommands(groundPrimitive, pickCommands); - } - function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { var boundingVolumes; if (frameState.mode === SceneMode.SCENE3D) { @@ -952,40 +566,35 @@ define([ var passes = frameState.passes; if (passes.render) { var colorLength = colorCommands.length; - for (var j = 0; j < colorLength; ++j) { - colorCommands[j].modelMatrix = modelMatrix; - colorCommands[j].boundingVolume = boundingVolumes[Math.floor(j / 3)]; - colorCommands[j].cull = cull; - colorCommands[j].debugShowBoundingVolume = debugShowBoundingVolume; - - commandList.push(colorCommands[j]); + for (var i = 0; i < colorLength; ++i) { + var colorCommand = colorCommands[i]; + colorCommand.owner = groundPrimitive; + colorCommand.modelMatrix = modelMatrix; + colorCommand.boundingVolume = boundingVolumes[Math.floor(i / 3)]; + colorCommand.cull = cull; + colorCommand.debugShowBoundingVolume = debugShowBoundingVolume; + + commandList.push(colorCommand); } } if (passes.pick) { - var primitive = groundPrimitive._primitive; + var primitive = groundPrimitive._primitive._primitive; var pickOffsets = primitive._pickOffsets; var length = pickOffsets.length * 3; pickCommands.length = length; - var pickIndex = 0; - for (var k = 0; k < length; k += 3) { - var pickOffset = pickOffsets[pickIndex++]; + for (var j = 0; j < length; ++j) { + var pickOffset = pickOffsets[Math.floor(j / 3)]; var bv = boundingVolumes[pickOffset.index]; - pickCommands[k].modelMatrix = modelMatrix; - pickCommands[k].boundingVolume = bv; - pickCommands[k].cull = cull; - - pickCommands[k + 1].modelMatrix = modelMatrix; - pickCommands[k + 1].boundingVolume = bv; - pickCommands[k + 1].cull = cull; - - pickCommands[k + 2].modelMatrix = modelMatrix; - pickCommands[k + 2].boundingVolume = bv; - pickCommands[k + 2].cull = cull; + var pickCommand = pickCommands[j]; + pickCommand.owner = groundPrimitive; + pickCommand.modelMatrix = modelMatrix; + pickCommand.boundingVolume = bv; + pickCommand.cull = cull; - commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]); + commandList.push(pickCommand); } } } @@ -1057,7 +666,6 @@ define([ var groundInstances = new Array(length); var i; - var color; var rectangle; for (i = 0; i < length; ++i) { instance = instances[i]; @@ -1077,19 +685,7 @@ define([ } instanceType = geometry.constructor; - if (defined(instanceType) && defined(instanceType.createShadowVolume)) { - var attributes = instance.attributes; - - //>>includeStart('debug', pragmas.debug); - 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; - } - //>>includeEnd('debug'); - } else { + if (!defined(instanceType) || !defined(instanceType.createShadowVolume)) { //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Not all of the geometry instances have GroundPrimitive support.'); //>>includeEnd('debug'); @@ -1110,8 +706,7 @@ define([ geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this), getComputeMaximumHeightFunction(this)), attributes : instance.attributes, - id : instance.id, - pickPrimitive : this + id : instance.id }); } @@ -1120,20 +715,11 @@ define([ primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) { createBoundingVolume(that, frameState, geometry); }; - primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) { - createRenderStates(that, context); - }; - primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) { - createShaderProgram(that, frameState); - }; - primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) { - createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands); - }; primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses); }; - this._primitive = new Primitive(primitiveOptions); + this._primitive = new ClassificationPrimitive(primitiveOptions); this._primitive.readyPromise.then(function(primitive) { that._ready = true; @@ -1150,18 +736,7 @@ define([ }); } - if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) { - this._debugShowShadowVolume = true; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(false)); - } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) { - this._debugShowShadowVolume = false; - this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true)); - this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true)); - this._rsColorPass = RenderState.fromCache(getColorRenderState(true)); - } - + this._primitive.debugShowShadowVolume = this.debugShowShadowVolume; this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume; this._primitive.update(frameState); }; @@ -1235,8 +810,6 @@ define([ */ GroundPrimitive.prototype.destroy = function() { this._primitive = this._primitive && this._primitive.destroy(); - this._sp = this._sp && this._sp.destroy(); - this._spPick = this._spPick && this._spPick.destroy(); return destroyObject(this); }; diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index 66ad743fc5d2..429f51d35200 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -21,6 +21,7 @@ define([ '../Core/RuntimeError', '../Core/Transforms', '../Core/TranslationRotationScale', + '../Renderer/Pass', './Cesium3DTileBatchTable', './Cesium3DTileFeature', './Cesium3DTileFeatureTable', @@ -48,6 +49,7 @@ define([ RuntimeError, Transforms, TranslationRotationScale, + Pass, Cesium3DTileBatchTable, Cesium3DTileFeature, Cesium3DTileFeatureTable, @@ -303,7 +305,8 @@ define([ gltf : undefined, basePath : undefined, incrementallyLoadTextures : false, - upAxis : content._tileset._gltfUpAxis + upAxis : content._tileset._gltfUpAxis, + opaquePass : Pass.CESIUM_3D_TILE // Draw opaque portions during the 3D Tiles pass }; if (gltfFormat === 0) { diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 2ff7c56c6fa2..0bd2a4d638bf 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -644,6 +644,12 @@ define([ */ this.cull = defaultValue(options.cull, true); + /** + * @private + * @readonly + */ + this.opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins this._boundingSphere = undefined; @@ -3483,7 +3489,7 @@ define([ uniformMap : uniformMap, renderState : rs, owner : owner, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE + pass : isTranslucent ? Pass.TRANSLUCENT : model.opaquePass }); var pickCommand; @@ -3522,7 +3528,7 @@ define([ uniformMap : pickUniformMap, renderState : rs, owner : owner, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE + pass : isTranslucent ? Pass.TRANSLUCENT : model.opaquePass }); } diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index 86f7586b6b50..514e34323921 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -16,6 +16,7 @@ define([ '../Renderer/Buffer', '../Renderer/BufferUsage', '../Renderer/DrawCommand', + '../Renderer/Pass', '../Renderer/ShaderSource', '../ThirdParty/when', './getAttributeOrUniformBySemantic', @@ -41,6 +42,7 @@ define([ Buffer, BufferUsage, DrawCommand, + Pass, ShaderSource, when, getAttributeOrUniformBySemantic, @@ -107,12 +109,15 @@ define([ this._instancingSupported = false; this._dynamic = defaultValue(options.dynamic, false); this._allowPicking = defaultValue(options.allowPicking, true); - this._cull = defaultValue(options.cull, true); // Undocumented option this._ready = false; this._readyPromise = when.defer(); this._state = LoadState.NEEDS_LOAD; this._dirty = false; + // Undocumented options + this._cull = defaultValue(options.cull, true); + this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + this._instances = createInstances(this, options.instances); // When the model instance collection is backed by an i3dm tile, @@ -603,7 +608,8 @@ define([ pickVertexShaderLoaded : undefined, pickFragmentShaderLoaded : undefined, pickUniformMapLoaded : undefined, - ignoreCommands : true + ignoreCommands : true, + opaquePass : collection._opaquePass }; if (allowPicking && !usesBatchTable) { diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index a872bef41470..0484e51bff28 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -189,6 +189,8 @@ define([ * * @see GeometryInstance * @see Appearance + * @see ClassificationPrimitive + * @see GroundPrimitive */ function Primitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); @@ -201,6 +203,7 @@ define([ * Changing this property after the primitive is rendered has no effect. *

* + * @readonly * @type GeometryInstance[]|GeometryInstance * * @default undefined @@ -1622,36 +1625,30 @@ define([ } } - function updateBoundingVolumes(primitive, frameState) { + Primitive._updateBoundingVolumes = function(primitive, frameState, modelMatrix) { + var i; + var length; + var boundingSphere; + // Update bounding volumes for primitives that are sized in pixels. // The pixel size in meters varies based on the distance from the camera. var pixelSize = primitive.appearance.pixelSize; if (defined(pixelSize)) { - var length = primitive._boundingSpheres.length; - for (var i = 0; i < length; ++i) { - var boundingSphere = primitive._boundingSpheres[i]; + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + boundingSphere = primitive._boundingSpheres[i]; var boundingSphereWC = primitive._boundingSphereWC[i]; var pixelSizeInMeters = frameState.camera.getPixelSize(boundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight); var sizeInMeters = pixelSizeInMeters * pixelSize; boundingSphereWC.radius = boundingSphere.radius + sizeInMeters; } } - } - - function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { - //>>includeStart('debug', pragmas.debug); - if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { - throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); - } - //>>includeEnd('debug'); - - updateBoundingVolumes(primitive, frameState); if (!Matrix4.equals(modelMatrix, primitive._modelMatrix)) { Matrix4.clone(modelMatrix, primitive._modelMatrix); - var length = primitive._boundingSpheres.length; - for (var i = 0; i < length; ++i) { - var boundingSphere = primitive._boundingSpheres[i]; + length = primitive._boundingSpheres.length; + for (i = 0; i < length; ++i) { + boundingSphere = primitive._boundingSpheres[i]; if (defined(boundingSphere)) { primitive._boundingSphereWC[i] = BoundingSphere.transform(boundingSphere, modelMatrix, primitive._boundingSphereWC[i]); if (!frameState.scene3DOnly) { @@ -1662,6 +1659,16 @@ define([ } } } + }; + + function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) { + //>>includeStart('debug', pragmas.debug); + if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { + throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.'); + } + //>>includeEnd('debug'); + + Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix); var boundingSpheres; if (frameState.mode === SceneMode.SCENE3D) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 67193b1ca034..f99853f1ac4e 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -1901,6 +1901,17 @@ define([ executeCommand(commands[j], scene, context, passState); } + us.updatePass(Pass.CESIUM_3D_TILE); + commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; + length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; + for (j = 0; j < length; ++j) { + executeCommand(commands[j], scene, context, passState); + } + + if (length > 0 && context.stencilBuffer) { + scene._stencilClearCommand.execute(context, passState); + } + if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && (scene.copyGlobeDepth || scene.debugShowGlobeDepth)) { globeDepth.update(context, passState); globeDepth.executeCopyDepth(context, passState); @@ -1929,13 +1940,6 @@ define([ } } - us.updatePass(Pass.CESIUM_3D_TILE); - commands = frustumCommands.commands[Pass.CESIUM_3D_TILE]; - length = frustumCommands.indices[Pass.CESIUM_3D_TILE]; - for (j = 0; j < length; ++j) { - executeCommand(commands[j], scene, context, passState); - } - // Execute commands in order by pass up to the translucent pass. // Translucent geometry needs special handling (sorting/OIT). var startPass = Pass.GROUND + 1; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 809cf0be80a3..a85fc04a548e 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -1,10 +1,13 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; -attribute vec3 extrudeDirection; attribute vec4 color; attribute float batchId; +#ifdef EXTRUDED_GEOMETRY +attribute vec3 extrudeDirection; + uniform float u_globeMinimumAltitude; +#endif // emulated noperspective varying float v_WindowZ; @@ -22,10 +25,14 @@ void main() v_color = color; 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 = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position); } diff --git a/Specs/Scene/ClassificationPrimitiveSpec.js b/Specs/Scene/ClassificationPrimitiveSpec.js new file mode 100644 index 000000000000..0bbbba8058ad --- /dev/null +++ b/Specs/Scene/ClassificationPrimitiveSpec.js @@ -0,0 +1,909 @@ +defineSuite([ + 'Scene/ClassificationPrimitive', + 'Core/BoxGeometry', + 'Core/Cartesian3', + 'Core/Color', + 'Core/ColorGeometryInstanceAttribute', + 'Core/destroyObject', + 'Core/DistanceDisplayConditionGeometryInstanceAttribute', + 'Core/Ellipsoid', + 'Core/GeometryInstance', + 'Core/HeadingPitchRange', + 'Core/Math', + 'Core/Matrix4', + 'Core/PolygonGeometry', + 'Core/Rectangle', + 'Core/RectangleGeometry', + 'Core/ShowGeometryInstanceAttribute', + 'Core/Transforms', + 'Renderer/Pass', + 'Scene/PerInstanceColorAppearance', + 'Scene/Primitive', + 'Specs/createScene', + 'Specs/pollToPromise' + ], function( + ClassificationPrimitive, + BoxGeometry, + Cartesian3, + Color, + ColorGeometryInstanceAttribute, + destroyObject, + DistanceDisplayConditionGeometryInstanceAttribute, + Ellipsoid, + GeometryInstance, + HeadingPitchRange, + CesiumMath, + Matrix4, + PolygonGeometry, + Rectangle, + RectangleGeometry, + ShowGeometryInstanceAttribute, + Transforms, + Pass, + PerInstanceColorAppearance, + Primitive, + createScene, + pollToPromise) { + 'use strict'; + + var scene; + + var ellipsoid; + var rectangle; + + var depthColor; + var boxColor; + + var boxInstance; + var primitive; + var depthPrimitive; + + beforeAll(function() { + scene = createScene(); + scene.fxaa = false; + + ellipsoid = Ellipsoid.WGS84; + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + function MockGlobePrimitive(primitive) { + this._primitive = primitive; + } + MockGlobePrimitive.prototype.update = function(frameState) { + var commandList = frameState.commandList; + var startLength = commandList.length; + this._primitive.update(frameState); + + for (var i = startLength; i < commandList.length; ++i) { + var command = commandList[i]; + command.pass = Pass.GLOBE; + } + }; + + MockGlobePrimitive.prototype.isDestroyed = function() { + return false; + }; + + MockGlobePrimitive.prototype.destroy = function() { + this._primitive.destroy(); + return destroyObject(this); + }; + + beforeEach(function() { + scene.morphTo3D(0); + + rectangle = Rectangle.fromDegrees(-80.0, 20.0, -70.0, 30.0); + + var depthColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 0.0, 1.0, 1.0)); + depthColor = depthColorAttribute.value; + var primitive = new Primitive({ + geometryInstances : new GeometryInstance({ + geometry : new RectangleGeometry({ + ellipsoid : ellipsoid, + rectangle : rectangle + }), + id : 'depth rectangle', + attributes : { + color : depthColorAttribute + } + }), + appearance : new PerInstanceColorAppearance({ + translucent : false, + flat : true + }), + asynchronous : false + }); + + // wrap rectangle primitive so it gets executed during the globe pass to lay down depth + depthPrimitive = new MockGlobePrimitive(primitive); + + var center = Rectangle.center(rectangle); + var origin = ellipsoid.cartographicToCartesian(center); + var modelMatrix = Transforms.eastNorthUpToFixedFrame(origin); + + var dimensions = new Cartesian3(1000000.0, 1000000.0, 1000000.0); + + var boxColorAttribute = ColorGeometryInstanceAttribute.fromColor(new Color(1.0, 1.0, 0.0, 1.0)); + boxColor = boxColorAttribute.value; + boxInstance = new GeometryInstance({ + geometry : BoxGeometry.fromDimensions({ + dimensions : dimensions + }), + modelMatrix : modelMatrix, + id : 'box', + attributes : { + color : boxColorAttribute + } + }); + }); + + afterEach(function() { + scene.groundPrimitives.removeAll(); + primitive = primitive && !primitive.isDestroyed() && primitive.destroy(); + depthPrimitive = depthPrimitive && !depthPrimitive.isDestroyed() && depthPrimitive.destroy(); + }); + + it('default constructs', function() { + primitive = new ClassificationPrimitive(); + expect(primitive.geometryInstances).not.toBeDefined(); + expect(primitive.show).toEqual(true); + expect(primitive.vertexCacheOptimize).toEqual(false); + expect(primitive.interleave).toEqual(false); + expect(primitive.compressVertices).toEqual(true); + expect(primitive.releaseGeometryInstances).toEqual(true); + expect(primitive.allowPicking).toEqual(true); + expect(primitive.asynchronous).toEqual(true); + expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.debugShowShadowVolume).toEqual(false); + }); + + it('constructs with options', function() { + var geometryInstances = []; + + primitive = new ClassificationPrimitive({ + geometryInstances : geometryInstances, + show : false, + vertexCacheOptimize : true, + interleave : true, + compressVertices : false, + releaseGeometryInstances : false, + allowPicking : false, + asynchronous : false, + debugShowBoundingVolume : true, + debugShowShadowVolume : true + }); + + expect(primitive.geometryInstances).toEqual(geometryInstances); + expect(primitive.show).toEqual(false); + expect(primitive.vertexCacheOptimize).toEqual(true); + expect(primitive.interleave).toEqual(true); + expect(primitive.compressVertices).toEqual(false); + expect(primitive.releaseGeometryInstances).toEqual(false); + expect(primitive.allowPicking).toEqual(false); + expect(primitive.asynchronous).toEqual(false); + expect(primitive.debugShowBoundingVolume).toEqual(true); + expect(primitive.debugShowShadowVolume).toEqual(true); + }); + + it('releases geometry instances when releaseGeometryInstances is true', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : true, + asynchronous : false + }); + + expect(primitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + expect(primitive.geometryInstances).not.toBeDefined(); + }); + + it('does not release geometry instances when releaseGeometryInstances is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + expect(primitive.geometryInstances).toBeDefined(); + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + expect(primitive.geometryInstances).toBeDefined(); + }); + + it('adds afterRender promise to frame state', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + releaseGeometryInstances : false, + asynchronous : false + }); + + scene.groundPrimitives.add(primitive); + scene.renderForSpecs(); + + return primitive.readyPromise.then(function(param) { + expect(param.ready).toBe(true); + }); + }); + + it('does not render when geometryInstances is undefined', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : undefined, + appearance : new PerInstanceColorAppearance(), + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('does not render when show is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + + frameState.commandList.length = 0; + primitive.update(frameState); + expect(frameState.afterRender.length).toEqual(1); + + frameState.afterRender[0](); + frameState.commandList.length = 0; + primitive.update(frameState); + expect(frameState.commandList.length).toBeGreaterThan(0); + + frameState.commandList.length = 0; + primitive.show = false; + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + it('does not render other than for the color or pick pass', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + var frameState = scene.frameState; + frameState.passes.render = false; + frameState.passes.pick = false; + + primitive.update(frameState); + expect(frameState.commandList.length).toEqual(0); + }); + + function verifyClassificationPrimitiveRender(primitive, color) { + 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(color); + } + + it('renders in 3D', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders in Columbus view when scene3DOnly is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + scene.morphToColumbusView(0); + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders in 2D when scene3DOnly is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + scene.morphTo2D(0); + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + + it('renders batched instances', 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 : boxColorAttribute + } + }); + + 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 : boxColorAttribute + } + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }); + + it('renders bounding volume with debugShowBoundingVolume', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false, + debugShowBoundingVolume : true + }); + + scene.groundPrimitives.add(primitive); + scene.camera.setView({ destination : rectangle }); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('renders shadow volume with debugShowShadowVolume', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false, + debugShowShadowVolume : true + }); + + scene.groundPrimitives.add(primitive); + scene.camera.setView({ destination : rectangle }); + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[1]).toBeGreaterThanOrEqualTo(0); + expect(rgba[2]).toBeGreaterThanOrEqualTo(0); + expect(rgba[3]).toEqual(255); + }); + }); + + it('get per instance attributes', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.color).toBeDefined(); + }); + + it('modify color instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var newColor = [255, 255, 255, 255]; + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.color).toBeDefined(); + attributes.color = newColor; + + verifyClassificationPrimitiveRender(primitive, newColor); + }); + + it('modify show instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + boxInstance.attributes.show = new ShowGeometryInstanceAttribute(true); + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + scene.groundPrimitives.destroyPrimitives = false; + scene.groundPrimitives.removeAll(); + scene.groundPrimitives.destroyPrimitives = true; + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.show).toBeDefined(); + attributes.show = [0]; + + verifyClassificationPrimitiveRender(primitive, depthColor); + }); + + it('get bounding sphere from per instance attribute', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(attributes.boundingSphere).toBeDefined(); + }); + + it('getGeometryInstanceAttributes returns same object each time', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + var attributes2 = primitive.getGeometryInstanceAttributes('box'); + expect(attributes).toBe(attributes2); + }); + + it('picking', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(scene).toPickAndCall(function(result) { + expect(result.id).toEqual('box'); + }); + }); + + it('does not pick when allowPicking is false', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + allowPicking : false, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(scene).notToPick(); + }); + + it('internally invalid asynchronous geometry resolves promise and sets ready', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + positions : [] + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor(Color.RED) + } + }), + compressVertices : false + }); + + var frameState = scene.frameState; + frameState.afterRender.length = 0; + return pollToPromise(function() { + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + return true; + } + primitive.update(frameState); + return false; + }).then(function() { + return primitive.readyPromise.then(function(arg) { + expect(arg).toBe(primitive); + expect(primitive.ready).toBe(true); + }); + }); + }); + + it('internally invalid synchronous geometry resolves promise and sets ready', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : new GeometryInstance({ + geometry : PolygonGeometry.fromPositions({ + positions : [] + }), + attributes: { + color: ColorGeometryInstanceAttribute.fromColor(Color.RED) + } + }), + asynchronous : false, + compressVertices : false + }); + + var frameState = scene.frameState; + frameState.afterRender.length = 0; + return pollToPromise(function() { + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + return true; + } + primitive.update(frameState); + return false; + }).then(function() { + return primitive.readyPromise.then(function(arg) { + expect(arg).toBe(primitive); + expect(primitive.ready).toBe(true); + }); + }); + }); + + it('update throws when batched instance colors are different', 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; + } + + 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' + }); + + primitive = new ClassificationPrimitive({ + geometryInstances : [boxInstance1, boxInstance2], + asynchronous : false + }); + + expect(function() { + verifyClassificationPrimitiveRender(primitive, boxColorAttribute.value); + }).toThrowDeveloperError(); + }); + + it('setting per instance attribute throws when value is undefined', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + var attributes = primitive.getGeometryInstanceAttributes('box'); + + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + + it('can disable picking when asynchronous', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : true, + allowPicking : false + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + primitive.update(frameState); + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + } + return primitive.ready; + }).then(function() { + var attributes = primitive.getGeometryInstanceAttributes('box'); + expect(function() { + attributes.color = undefined; + }).toThrowDeveloperError(); + }); + }); + + it('getGeometryInstanceAttributes throws without id', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(function() { + primitive.getGeometryInstanceAttributes(); + }).toThrowDeveloperError(); + }); + + it('getGeometryInstanceAttributes throws if update was not called', function() { + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + expect(function() { + primitive.getGeometryInstanceAttributes('box'); + }).toThrowDeveloperError(); + }); + + it('getGeometryInstanceAttributes returns undefined if id does not exist', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance, + asynchronous : false + }); + + verifyClassificationPrimitiveRender(primitive, boxColor); + + expect(primitive.getGeometryInstanceAttributes('unknown')).not.toBeDefined(); + }); + + it('isDestroyed', function() { + primitive = new ClassificationPrimitive(); + expect(primitive.isDestroyed()).toEqual(false); + primitive.destroy(); + expect(primitive.isDestroyed()).toEqual(true); + }); + + it('renders when using asynchronous pipeline', function() { + if (!ClassificationPrimitive.isSupported(scene)) { + return; + } + + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance + }); + + var frameState = scene.frameState; + + return pollToPromise(function() { + primitive.update(frameState); + if (frameState.afterRender.length > 0) { + frameState.afterRender[0](); + } + return primitive.ready; + }).then(function() { + verifyClassificationPrimitiveRender(primitive, boxColor); + }); + }); + + it('destroy before asynchonous pipeline is complete', function() { + primitive = new ClassificationPrimitive({ + geometryInstances : boxInstance + }); + + var frameState = scene.frameState; + primitive.update(frameState); + + primitive.destroy(); + expect(primitive.isDestroyed()).toEqual(true); + }); +}, 'WebGL'); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index 54c22f83c35a..f80c0fcdb7fa 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -152,6 +152,7 @@ defineSuite([ expect(primitive.allowPicking).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.debugShowShadowVolume).toEqual(false); }); it('constructs with options', function() { @@ -166,7 +167,8 @@ defineSuite([ releaseGeometryInstances : false, allowPicking : false, asynchronous : false, - debugShowBoundingVolume : true + debugShowBoundingVolume : true, + debugShowShadowVolume : true }); expect(primitive.geometryInstances).toEqual(geometryInstances); @@ -178,6 +180,7 @@ defineSuite([ expect(primitive.allowPicking).toEqual(false); expect(primitive.asynchronous).toEqual(false); expect(primitive.debugShowBoundingVolume).toEqual(true); + expect(primitive.debugShowShadowVolume).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() {