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 @@ + + +
+ + + + + ++ * 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] Whentrue
, 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.
+ *
+ * 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, { + /** + * Whentrue
, 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.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.
+ *
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.
+ *
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() {