UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,165 lines (1,033 loc) 92.7 kB
import BoundingSphere from '../Core/BoundingSphere.js'; import Cartesian2 from '../Core/Cartesian2.js'; import Cartesian3 from '../Core/Cartesian3.js'; import Cartesian4 from '../Core/Cartesian4.js'; import Cartographic from '../Core/Cartographic.js'; import clone from '../Core/clone.js'; import Color from '../Core/Color.js'; import combine from '../Core/combine.js'; import ComponentDatatype from '../Core/ComponentDatatype.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import destroyObject from '../Core/destroyObject.js'; import DeveloperError from '../Core/DeveloperError.js'; import EncodedCartesian3 from '../Core/EncodedCartesian3.js'; import FeatureDetection from '../Core/FeatureDetection.js'; import Geometry from '../Core/Geometry.js'; import GeometryAttribute from '../Core/GeometryAttribute.js'; import GeometryAttributes from '../Core/GeometryAttributes.js'; import GeometryOffsetAttribute from '../Core/GeometryOffsetAttribute.js'; import Intersect from '../Core/Intersect.js'; import Matrix4 from '../Core/Matrix4.js'; import Plane from '../Core/Plane.js'; import RuntimeError from '../Core/RuntimeError.js'; import subdivideArray from '../Core/subdivideArray.js'; import TaskProcessor from '../Core/TaskProcessor.js'; import BufferUsage from '../Renderer/BufferUsage.js'; import ContextLimits from '../Renderer/ContextLimits.js'; import DrawCommand from '../Renderer/DrawCommand.js'; import Pass from '../Renderer/Pass.js'; import RenderState from '../Renderer/RenderState.js'; import ShaderProgram from '../Renderer/ShaderProgram.js'; import ShaderSource from '../Renderer/ShaderSource.js'; import VertexArray from '../Renderer/VertexArray.js'; import when from '../ThirdParty/when.js'; import BatchTable from './BatchTable.js'; import CullFace from './CullFace.js'; import DepthFunction from './DepthFunction.js'; import PrimitivePipeline from './PrimitivePipeline.js'; import PrimitiveState from './PrimitiveState.js'; import SceneMode from './SceneMode.js'; import ShadowMode from './ShadowMode.js'; /** * A primitive represents geometry in the {@link Scene}. The geometry can be from a single {@link GeometryInstance} * as shown in example 1 below, or from an array of instances, even if the geometry is from different * geometry types, e.g., an {@link RectangleGeometry} and an {@link EllipsoidGeometry} as shown in Code Example 2. * <p> * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including * {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement, * and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix * and match most of them and add a new geometry or appearance independently of each other. * </p> * <p> * Combining multiple instances into one primitive is called batching, and significantly improves performance for static data. * Instances can be individually picked; {@link Scene#pick} returns their {@link GeometryInstance#id}. Using * per-instance appearances like {@link PerInstanceColorAppearance}, each instance can also have a unique color. * </p> * <p> * {@link Geometry} can either be created and batched on a web worker or the main thread. The first two examples * show geometry that will be created on a web worker by using the descriptions of the geometry. The third example * shows how to create the geometry on the main thread by explicitly calling the <code>createGeometry</code> method. * </p> * * @alias Primitive * @constructor * * @param {Object} [options] Object with the following properties: * @param {GeometryInstance[]|GeometryInstance} [options.geometryInstances] The geometry instances - or a single geometry instance - to render. * @param {Appearance} [options.appearance] The appearance used to render the primitive. * @param {Appearance} [options.depthFailAppearance] The appearance used to shade this primitive when it fails the depth test. * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates. * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory. * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory. * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved. * @param {Boolean} [options.cull=true] When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume. Set this to <code>false</code> for a small performance gain if you are manually culling the primitive. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from light sources. * * @example * // 1. Draw a translucent ellipse on the surface with a checkerboard pattern * var instance = new Cesium.GeometryInstance({ * geometry : new Cesium.EllipseGeometry({ * center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0), * semiMinorAxis : 500000.0, * semiMajorAxis : 1000000.0, * rotation : Cesium.Math.PI_OVER_FOUR, * vertexFormat : Cesium.VertexFormat.POSITION_AND_ST * }), * id : 'object returned when this instance is picked and to get/set per-instance attributes' * }); * scene.primitives.add(new Cesium.Primitive({ * geometryInstances : instance, * appearance : new Cesium.EllipsoidSurfaceAppearance({ * material : Cesium.Material.fromType('Checkerboard') * }) * })); * * @example * // 2. Draw different instances each with a unique color * var rectangleInstance = new Cesium.GeometryInstance({ * geometry : new Cesium.RectangleGeometry({ * rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0), * vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT * }), * id : 'rectangle', * attributes : { * color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5) * } * }); * var ellipsoidInstance = new Cesium.GeometryInstance({ * geometry : new Cesium.EllipsoidGeometry({ * radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0), * vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL * }), * modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( * Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()), * id : 'ellipsoid', * attributes : { * color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA) * } * }); * scene.primitives.add(new Cesium.Primitive({ * geometryInstances : [rectangleInstance, ellipsoidInstance], * appearance : new Cesium.PerInstanceColorAppearance() * })); * * @example * // 3. Create the geometry on the main thread. * scene.primitives.add(new Cesium.Primitive({ * geometryInstances : new Cesium.GeometryInstance({ * geometry : Cesium.EllipsoidGeometry.createGeometry(new Cesium.EllipsoidGeometry({ * radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0), * vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL * })), * modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( * Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()), * id : 'ellipsoid', * attributes : { * color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA) * } * }), * appearance : new Cesium.PerInstanceColorAppearance() * })); * * @see GeometryInstance * @see Appearance * @see ClassificationPrimitive * @see GroundPrimitive */ function Primitive(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); /** * The geometry instances rendered with this primitive. This may * be <code>undefined</code> if <code>options.releaseGeometryInstances</code> * is <code>true</code> when the primitive is constructed. * <p> * Changing this property after the primitive is rendered has no effect. * </p> * * @readonly * @type GeometryInstance[]|GeometryInstance * * @default undefined */ this.geometryInstances = options.geometryInstances; /** * The {@link Appearance} used to shade this primitive. Each geometry * instance is shaded with the same appearance. Some appearances, like * {@link PerInstanceColorAppearance} allow giving each instance unique * properties. * * @type Appearance * * @default undefined */ this.appearance = options.appearance; this._appearance = undefined; this._material = undefined; /** * The {@link Appearance} used to shade this primitive when it fails the depth test. Each geometry * instance is shaded with the same appearance. Some appearances, like * {@link PerInstanceColorAppearance} allow giving each instance unique * properties. * * <p> * When using an appearance that requires a color attribute, like PerInstanceColorAppearance, * add a depthFailColor per-instance attribute instead. * </p> * * <p> * Requires the EXT_frag_depth WebGL extension to render properly. If the extension is not supported, * there may be artifacts. * </p> * @type Appearance * * @default undefined */ this.depthFailAppearance = options.depthFailAppearance; this._depthFailAppearance = undefined; this._depthFailMaterial = undefined; /** * The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates. * When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth's WGS84 coordinates. * Local reference frames can be used by providing a different transformation matrix, like that returned * by {@link Transforms.eastNorthUpToFixedFrame}. * * <p> * This property is only supported in 3D mode. * </p> * * @type Matrix4 * * @default Matrix4.IDENTITY * * @example * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); * p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); */ this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this._modelMatrix = new Matrix4(); /** * 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._vertexCacheOptimize = defaultValue(options.vertexCacheOptimize, false); this._interleave = defaultValue(options.interleave, false); this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true); this._allowPicking = defaultValue(options.allowPicking, true); this._asynchronous = defaultValue(options.asynchronous, true); this._compressVertices = defaultValue(options.compressVertices, true); /** * When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands * based on their bounding volume. Set this to <code>false</code> for a small performance gain * if you are manually culling the primitive. * * @type {Boolean} * * @default true */ this.cull = defaultValue(options.cull, true); /** * This property is for debugging only; it is not for production use nor is it optimized. * <p> * Draws the bounding sphere for each draw command in the primitive. * </p> * * @type {Boolean} * * @default false */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); /** * @private */ this.rtcCenter = options.rtcCenter; //>>includeStart('debug', pragmas.debug); if (defined(this.rtcCenter) && (!defined(this.geometryInstances) || (Array.isArray(this.geometryInstances) && this.geometryInstances.length !== 1))) { throw new DeveloperError('Relative-to-center rendering only supports one geometry instance.'); } //>>includeEnd('debug'); /** * Determines whether this primitive casts or receives shadows from light sources. * * @type {ShadowMode} * * @default ShadowMode.DISABLED */ this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED); this._translucent = undefined; this._state = PrimitiveState.READY; this._geometries = []; this._error = undefined; this._numberOfInstances = 0; this._boundingSpheres = []; this._boundingSphereWC = []; this._boundingSphereCV = []; this._boundingSphere2D = []; this._boundingSphereMorph = []; this._perInstanceAttributeCache = []; this._instanceIds = []; this._lastPerInstanceAttributeIndex = 0; this._va = []; this._attributeLocations = undefined; this._primitiveType = undefined; this._frontFaceRS = undefined; this._backFaceRS = undefined; this._sp = undefined; this._depthFailAppearance = undefined; this._spDepthFail = undefined; this._frontFaceDepthFailRS = undefined; this._backFaceDepthFailRS = undefined; this._pickIds = []; this._colorCommands = []; this._pickCommands = []; this._createBoundingVolumeFunction = options._createBoundingVolumeFunction; this._createRenderStatesFunction = options._createRenderStatesFunction; this._createShaderProgramFunction = options._createShaderProgramFunction; this._createCommandsFunction = options._createCommandsFunction; this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction; this._createPickOffsets = options._createPickOffsets; this._pickOffsets = undefined; this._createGeometryResults = undefined; this._ready = false; this._readyPromise = when.defer(); this._batchTable = undefined; this._batchTableAttributeIndices = undefined; this._offsetInstanceExtend = undefined; this._batchTableOffsetAttribute2DIndex = undefined; this._batchTableOffsetsUpdated = false; this._instanceBoundingSpheres = undefined; this._instanceBoundingSpheresCV = undefined; this._tempBoundingSpheres = undefined; this._recomputeBoundingSpheres = false; this._batchTableBoundingSpheresUpdated = false; this._batchTableBoundingSphereAttributeIndices = undefined; } Object.defineProperties(Primitive.prototype, { /** * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default true */ vertexCacheOptimize : { get : function() { return this._vertexCacheOptimize; } }, /** * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default false */ interleave : { get : function() { return this._interleave; } }, /** * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default true */ releaseGeometryInstances : { get : function() { return this._releaseGeometryInstances; } }, /** * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved. * * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default true */ allowPicking : { get : function() { return this._allowPicking; } }, /** * Determines if the geometry instances will be created and batched on a web worker. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default true */ asynchronous : { get : function() { return this._asynchronous; } }, /** * When <code>true</code>, geometry vertices are compressed, which will save memory. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly * * @default true */ compressVertices : { get : function() { return this._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 Primitive#update} * is called. * * @memberof Primitive.prototype * * @type {Boolean} * @readonly */ ready : { get : function() { return this._ready; } }, /** * Gets a promise that resolves when the primitive is ready to render. * @memberof Primitive.prototype * @type {Promise.<Primitive>} * @readonly */ readyPromise : { get : function() { return this._readyPromise.promise; } } }); function getCommonPerInstanceAttributeNames(instances) { var length = instances.length; var attributesInAllInstances = []; var attributes0 = instances[0].attributes; var name; for (name in attributes0) { if (attributes0.hasOwnProperty(name) && defined(attributes0[name])) { var attribute = attributes0[name]; var inAllInstances = true; // Does this same attribute exist in all instances? for (var i = 1; i < length; ++i) { var otherAttribute = instances[i].attributes[name]; if (!defined(otherAttribute) || (attribute.componentDatatype !== otherAttribute.componentDatatype) || (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || (attribute.normalize !== otherAttribute.normalize)) { inAllInstances = false; break; } } if (inAllInstances) { attributesInAllInstances.push(name); } } } return attributesInAllInstances; } var scratchGetAttributeCartesian2 = new Cartesian2(); var scratchGetAttributeCartesian3 = new Cartesian3(); var scratchGetAttributeCartesian4 = new Cartesian4(); function getAttributeValue(value) { var componentsPerAttribute = value.length; if (componentsPerAttribute === 1) { return value[0]; } else if (componentsPerAttribute === 2) { return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2); } else if (componentsPerAttribute === 3) { return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3); } else if (componentsPerAttribute === 4) { return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4); } } function createBatchTable(primitive, context) { var geometryInstances = primitive.geometryInstances; var instances = (Array.isArray(geometryInstances)) ? geometryInstances : [geometryInstances]; var numberOfInstances = instances.length; if (numberOfInstances === 0) { return; } var names = getCommonPerInstanceAttributeNames(instances); var length = names.length; var attributes = []; var attributeIndices = {}; var boundingSphereAttributeIndices = {}; var offset2DIndex; var firstInstance = instances[0]; var instanceAttributes = firstInstance.attributes; var i; var name; var attribute; for (i = 0; i < length; ++i) { name = names[i]; attribute = instanceAttributes[name]; attributeIndices[name] = i; attributes.push({ functionName : 'czm_batchTable_' + name, componentDatatype : attribute.componentDatatype, componentsPerAttribute : attribute.componentsPerAttribute, normalize : attribute.normalize }); } if (names.indexOf('distanceDisplayCondition') !== -1) { attributes.push({ functionName : 'czm_batchTable_boundingSphereCenter3DHigh', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3 }, { functionName : 'czm_batchTable_boundingSphereCenter3DLow', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3 }, { functionName : 'czm_batchTable_boundingSphereCenter2DHigh', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3 }, { functionName : 'czm_batchTable_boundingSphereCenter2DLow', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3 }, { functionName : 'czm_batchTable_boundingSphereRadius', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1 }); boundingSphereAttributeIndices.center3DHigh = attributes.length - 5; boundingSphereAttributeIndices.center3DLow = attributes.length - 4; boundingSphereAttributeIndices.center2DHigh = attributes.length - 3; boundingSphereAttributeIndices.center2DLow = attributes.length - 2; boundingSphereAttributeIndices.radius = attributes.length - 1; } if (names.indexOf('offset') !== -1) { attributes.push({ functionName : 'czm_batchTable_offset2D', componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3 }); offset2DIndex = attributes.length - 1; } attributes.push({ functionName : 'czm_batchTable_pickColor', componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, normalize : true }); var attributesLength = attributes.length; var batchTable = new BatchTable(context, attributes, numberOfInstances); for (i = 0; i < numberOfInstances; ++i) { var instance = instances[i]; instanceAttributes = instance.attributes; for (var j = 0; j < length; ++j) { name = names[j]; attribute = instanceAttributes[name]; var value = getAttributeValue(attribute.value); var attributeIndex = attributeIndices[name]; batchTable.setBatchedAttribute(i, attributeIndex, value); } var pickObject = { primitive : defaultValue(instance.pickPrimitive, primitive) }; if (defined(instance.id)) { pickObject.id = instance.id; } var pickId = context.createPickId(pickObject); primitive._pickIds.push(pickId); var pickColor = pickId.color; var color = scratchGetAttributeCartesian4; color.x = Color.floatToByte(pickColor.red); color.y = Color.floatToByte(pickColor.green); color.z = Color.floatToByte(pickColor.blue); color.w = Color.floatToByte(pickColor.alpha); batchTable.setBatchedAttribute(i, attributesLength - 1, color); } primitive._batchTable = batchTable; primitive._batchTableAttributeIndices = attributeIndices; primitive._batchTableBoundingSphereAttributeIndices = boundingSphereAttributeIndices; primitive._batchTableOffsetAttribute2DIndex = offset2DIndex; } function cloneAttribute(attribute) { var clonedValues; if (Array.isArray(attribute.values)) { clonedValues = attribute.values.slice(0); } else { clonedValues = new attribute.values.constructor(attribute.values); } return new GeometryAttribute({ componentDatatype : attribute.componentDatatype, componentsPerAttribute : attribute.componentsPerAttribute, normalize : attribute.normalize, values : clonedValues }); } function cloneGeometry(geometry) { var attributes = geometry.attributes; var newAttributes = new GeometryAttributes(); for (var property in attributes) { if (attributes.hasOwnProperty(property) && defined(attributes[property])) { newAttributes[property] = cloneAttribute(attributes[property]); } } var indices; if (defined(geometry.indices)) { var sourceValues = geometry.indices; if (Array.isArray(sourceValues)) { indices = sourceValues.slice(0); } else { indices = new sourceValues.constructor(sourceValues); } } return new Geometry({ attributes : newAttributes, indices : indices, primitiveType : geometry.primitiveType, boundingSphere : BoundingSphere.clone(geometry.boundingSphere) }); } function cloneInstance(instance, geometry) { return { geometry : geometry, attributes: instance.attributes, modelMatrix : Matrix4.clone(instance.modelMatrix), pickPrimitive : instance.pickPrimitive, id : instance.id }; } var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g; Primitive._modifyShaderPosition = function(primitive, vertexShaderSource, scene3DOnly) { var match; var forwardDecl = ''; var attributes = ''; var computeFunctions = ''; while ((match = positionRegex.exec(vertexShaderSource)) !== null) { var name = match[1]; var functionName = 'vec4 czm_compute' + name[0].toUpperCase() + name.substr(1) + '()'; // Don't forward-declare czm_computePosition because computePosition.glsl already does. if (functionName !== 'vec4 czm_computePosition()') { forwardDecl += functionName + ';\n'; } if (!defined(primitive.rtcCenter)) { // Use GPU RTE if (!scene3DOnly) { attributes += 'attribute vec3 ' + name + '2DHigh;\n' + 'attribute vec3 ' + name + '2DLow;\n'; computeFunctions += functionName + '\n' + '{\n' + ' vec4 p;\n' + ' if (czm_morphTime == 1.0)\n' + ' {\n' + ' p = czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' + ' }\n' + ' else if (czm_morphTime == 0.0)\n' + ' {\n' + ' p = czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy);\n' + ' }\n' + ' else\n' + ' {\n' + ' p = czm_columbusViewMorph(\n' + ' czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy),\n' + ' czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow),\n' + ' czm_morphTime);\n' + ' }\n' + ' return p;\n' + '}\n\n'; } else { computeFunctions += functionName + '\n' + '{\n' + ' return czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' + '}\n\n'; } } else { // Use RTC vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DHigh;/g, ''); vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DLow;/g, ''); forwardDecl += 'uniform mat4 u_modifiedModelView;\n'; attributes += 'attribute vec4 position;\n'; computeFunctions += functionName + '\n' + '{\n' + ' return u_modifiedModelView * position;\n' + '}\n\n'; vertexShaderSource = vertexShaderSource.replace(/czm_modelViewRelativeToEye\s+\*\s+/g, ''); vertexShaderSource = vertexShaderSource.replace(/czm_modelViewProjectionRelativeToEye/g, 'czm_projection'); } } return [forwardDecl, attributes, vertexShaderSource, computeFunctions].join('\n'); }; Primitive._appendShowToShader = function(primitive, vertexShaderSource) { if (!defined(primitive._batchTableAttributeIndices.show)) { return vertexShaderSource; } var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main'); var showMain = 'void main() \n' + '{ \n' + ' czm_non_show_main(); \n' + ' gl_Position *= czm_batchTable_show(batchId); \n' + '}'; return renamedVS + '\n' + showMain; }; Primitive._updateColorAttribute = function(primitive, vertexShaderSource, isDepthFail) { // some appearances have a color attribute for per vertex color. // only remove if color is a per instance attribute. if (!defined(primitive._batchTableAttributeIndices.color) && !defined(primitive._batchTableAttributeIndices.depthFailColor)) { return vertexShaderSource; } if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) { return vertexShaderSource; } //>>includeStart('debug', pragmas.debug); if (isDepthFail && !defined(primitive._batchTableAttributeIndices.depthFailColor)) { throw new DeveloperError('A depthFailColor per-instance attribute is required when using a depth fail appearance that uses a color attribute.'); } //>>includeEnd('debug'); var modifiedVS = vertexShaderSource; modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, ''); if (!isDepthFail) { modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2'); } else { modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_depthFailColor(batchId)$2'); } return modifiedVS; }; function appendPickToVertexShader(source) { var renamedVS = ShaderSource.replaceMain(source, 'czm_non_pick_main'); var pickMain = 'varying vec4 v_pickColor; \n' + 'void main() \n' + '{ \n' + ' czm_non_pick_main(); \n' + ' v_pickColor = czm_batchTable_pickColor(batchId); \n' + '}'; return renamedVS + '\n' + pickMain; } function appendPickToFragmentShader(source) { return 'varying vec4 v_pickColor;\n' + source; } Primitive._updatePickColorAttribute = function(source) { var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, ''); vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2'); return vsPick; }; Primitive._appendOffsetToShader = function(primitive, vertexShaderSource) { if (!defined(primitive._batchTableAttributeIndices.offset)) { return vertexShaderSource; } var attr = 'attribute float batchId;\n'; attr += 'attribute float applyOffset;'; var modifiedShader = vertexShaderSource.replace(/attribute\s+float\s+batchId;/g, attr); var str = 'vec4 $1 = czm_computePosition();\n'; str += ' if (czm_sceneMode == czm_sceneMode3D)\n'; str += ' {\n'; str += ' $1 = $1 + vec4(czm_batchTable_offset(batchId) * applyOffset, 0.0);'; str += ' }\n'; str += ' else\n'; str += ' {\n'; str += ' $1 = $1 + vec4(czm_batchTable_offset2D(batchId) * applyOffset, 0.0);'; str += ' }\n'; modifiedShader = modifiedShader.replace(/vec4\s+([A-Za-z0-9_]+)\s+=\s+czm_computePosition\(\);/g, str); return modifiedShader; }; Primitive._appendDistanceDisplayConditionToShader = function(primitive, vertexShaderSource, scene3DOnly) { if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition)) { return vertexShaderSource; } var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_distanceDisplayCondition_main'); var distanceDisplayConditionMain = 'void main() \n' + '{ \n' + ' czm_non_distanceDisplayCondition_main(); \n' + ' vec2 distanceDisplayCondition = czm_batchTable_distanceDisplayCondition(batchId);\n' + ' vec3 boundingSphereCenter3DHigh = czm_batchTable_boundingSphereCenter3DHigh(batchId);\n' + ' vec3 boundingSphereCenter3DLow = czm_batchTable_boundingSphereCenter3DLow(batchId);\n' + ' float boundingSphereRadius = czm_batchTable_boundingSphereRadius(batchId);\n'; if (!scene3DOnly) { distanceDisplayConditionMain += ' vec3 boundingSphereCenter2DHigh = czm_batchTable_boundingSphereCenter2DHigh(batchId);\n' + ' vec3 boundingSphereCenter2DLow = czm_batchTable_boundingSphereCenter2DLow(batchId);\n' + ' vec4 centerRTE;\n' + ' if (czm_morphTime == 1.0)\n' + ' {\n' + ' centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n' + ' }\n' + ' else if (czm_morphTime == 0.0)\n' + ' {\n' + ' centerRTE = czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy);\n' + ' }\n' + ' else\n' + ' {\n' + ' centerRTE = czm_columbusViewMorph(\n' + ' czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy),\n' + ' czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow),\n' + ' czm_morphTime);\n' + ' }\n'; } else { distanceDisplayConditionMain += ' vec4 centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n'; } distanceDisplayConditionMain += ' float radiusSq = boundingSphereRadius * boundingSphereRadius; \n' + ' float distanceSq; \n' + ' if (czm_sceneMode == czm_sceneMode2D) \n' + ' { \n' + ' distanceSq = czm_eyeHeight2D.y - radiusSq; \n' + ' } \n' + ' else \n' + ' { \n' + ' distanceSq = dot(centerRTE.xyz, centerRTE.xyz) - radiusSq; \n' + ' } \n' + ' distanceSq = max(distanceSq, 0.0); \n' + ' float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; \n' + ' float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; \n' + ' float show = (distanceSq >= nearSq && distanceSq <= farSq) ? 1.0 : 0.0; \n' + ' gl_Position *= show; \n' + '}'; return renamedVS + '\n' + distanceDisplayConditionMain; }; function modifyForEncodedNormals(primitive, vertexShaderSource) { if (!primitive.compressVertices) { return vertexShaderSource; } var containsNormal = vertexShaderSource.search(/attribute\s+vec3\s+normal;/g) !== -1; var containsSt = vertexShaderSource.search(/attribute\s+vec2\s+st;/g) !== -1; if (!containsNormal && !containsSt) { return vertexShaderSource; } var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1; var containsBitangent = vertexShaderSource.search(/attribute\s+vec3\s+bitangent;/g) !== -1; var numComponents = containsSt && containsNormal ? 2.0 : 1.0; numComponents += containsTangent || containsBitangent ? 1 : 0; var type = (numComponents > 1) ? 'vec' + numComponents : 'float'; var attributeName = 'compressedAttributes'; var attributeDecl = 'attribute ' + type + ' ' + attributeName + ';'; var globalDecl = ''; var decode = ''; if (containsSt) { globalDecl += 'vec2 st;\n'; var stComponent = numComponents > 1 ? attributeName + '.x' : attributeName; decode += ' st = czm_decompressTextureCoordinates(' + stComponent + ');\n'; } if (containsNormal && containsTangent && containsBitangent) { globalDecl += 'vec3 normal;\n' + 'vec3 tangent;\n' + 'vec3 bitangent;\n'; decode += ' czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, bitangent);\n'; } else { if (containsNormal) { globalDecl += 'vec3 normal;\n'; decode += ' normal = czm_octDecode(' + attributeName + (numComponents > 1 ? '.' + (containsSt ? 'y' : 'x') : '') + ');\n'; } if (containsTangent) { globalDecl += 'vec3 tangent;\n'; decode += ' tangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n'; } if (containsBitangent) { globalDecl += 'vec3 bitangent;\n'; decode += ' bitangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n'; } } var modifiedVS = vertexShaderSource; modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, ''); modifiedVS = modifiedVS.replace(/attribute\s+vec2\s+st;/g, ''); modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+tangent;/g, ''); modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+bitangent;/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 depthClampVS(vertexShaderSource) { var modifiedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_depth_clamp_main'); // The varying should be surround by #ifdef GL_EXT_frag_depth as an optimization. // It is not to workaround an issue with Edge: // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12120362/ modifiedVS += 'varying float v_WindowZ;\n' + 'void main() {\n' + ' czm_non_depth_clamp_main();\n' + ' vec4 position = gl_Position;\n' + ' v_WindowZ = (0.5 * (position.z / position.w) + 0.5) * position.w;\n' + ' position.z = min(position.z, position.w);\n' + ' gl_Position = position;\n' + '}\n'; return modifiedVS; } function depthClampFS(fragmentShaderSource) { var modifiedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_non_depth_clamp_main'); modifiedFS += 'varying float v_WindowZ;\n' + 'void main() {\n' + ' czm_non_depth_clamp_main();\n' + '#if defined(GL_EXT_frag_depth)\n' + ' #if defined(LOG_DEPTH)\n' + ' czm_writeLogDepth();\n' + ' #else\n' + ' gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n' + ' #endif\n' + '#endif\n' + '}\n'; modifiedFS = '#ifdef GL_EXT_frag_depth\n' + '#extension GL_EXT_frag_depth : enable\n' + '#endif\n' + modifiedFS; return modifiedFS; } function validateShaderMatching(shaderProgram, attributeLocations) { // For a VAO and shader program to be compatible, the VAO must have // all active attribute in the shader program. The VAO may have // extra attributes with the only concern being a potential // performance hit due to extra memory bandwidth and cache pollution. // The shader source could have extra attributes that are not used, // but there is no guarantee they will be optimized out. // // Here, we validate that the VAO has all attributes required // to match the shader program. var shaderAttributes = shaderProgram.vertexAttributes; //>>includeStart('debug', pragmas.debug); for (var name in shaderAttributes) { if (shaderAttributes.hasOwnProperty(name)) { if (!defined(attributeLocations[name])) { throw new DeveloperError('Appearance/Geometry mismatch. The appearance requires vertex shader attribute input \'' + name + '\', which was not computed as part of the Geometry. Use the appearance\'s vertexFormat property when constructing the geometry.'); } } } //>>includeEnd('debug'); } function getUniformFunction(uniforms, name) { return function() { return uniforms[name]; }; } var numberOfCreationWorkers = Math.max(FeatureDetection.hardwareConcurrency - 1, 1); var createGeometryTaskProcessors; var combineGeometryTaskProcessor = new TaskProcessor('combineGeometry', Number.POSITIVE_INFINITY); function loadAsynchronous(primitive, frameState) { var instances; var geometry; var i; var j; var instanceIds = primitive._instanceIds; if (primitive._state === PrimitiveState.READY) { instances = (Array.isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; var length = primitive._numberOfInstances = instances.length; var promises = []; var subTasks = []; for (i = 0; i < length; ++i) { geometry = instances[i].geometry; instanceIds.push(instances[i].id); //>>includeStart('debug', pragmas.debug); if (!defined(geometry._workerName)) { throw new DeveloperError('_workerName must be defined for asynchronous geometry.'); } //>>includeEnd('debug'); subTasks.push({ moduleName : geometry._workerName, geometry : geometry }); } if (!defined(createGeometryTaskProcessors)) { createGeometryTaskProcessors = new Array(numberOfCreationWorkers); for (i = 0; i < numberOfCreationWorkers; i++) { createGeometryTaskProcessors[i] = new TaskProcessor('createGeometry', Number.POSITIVE_INFINITY); } } var subTask; subTasks = subdivideArray(subTasks, numberOfCreationWorkers); for (i = 0; i < subTasks.length; i++) { var packedLength = 0; var workerSubTasks = subTasks[i]; var workerSubTasksLength = workerSubTasks.length; for (j = 0; j < workerSubTasksLength; ++j) { subTask = workerSubTasks[j]; geometry = subTask.geometry; if (defined(geometry.constructor.pack)) { subTask.offset = packedLength; packedLength += defaultValue(geometry.constructor.packedLength, geometry.packedLength); } } var subTaskTransferableObjects; if (packedLength > 0) { var array = new Float64Array(packedLength); subTaskTransferableObjects = [array.buffer]; for (j = 0; j < workerSubTasksLength; ++j) { subTask = workerSubTasks[j]; geometry = subTask.geometry; if (defined(geometry.constructor.pack)) { geometry.constructor.pack(geometry, array, subTask.offset); subTask.geometry = array; } } } promises.push(createGeometryTaskProcessors[i].scheduleTask({ subTasks : subTasks[i] }, subTaskTransferableObjects)); } primitive._state = PrimitiveState.CREATING; when.all(promises, function(results) { primitive._createGeometryResults = results; primitive._state = PrimitiveState.CREATED; }).otherwise(function(error) { setReady(primitive, frameState, PrimitiveState.FAILED, error); }); } else if (primitive._state === PrimitiveState.CREATED) { var transferableObjects = []; instances = (Array.isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({ createGeometryResults : primitive._createGeometryResults, instances : instances, ellipsoid : projection.ellipsoid, projection : projection, elementIndexUintSupported : frameState.context.elementIndexUint, scene3DOnly : scene3DOnly, vertexCach