UNPKG

cesium

Version:

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

1,520 lines (1,366 loc) 81.4 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" ); modifiedVS += "void main() {\n" + " czm_non_depth_clamp_main();\n" + " gl_Position = czm_depthClamp(gl_Position);" + "}\n"; return modifiedVS; } function depthClampFS(fragmentShaderSource) { var modifiedFS = ShaderSource.replaceMain( fragmentShaderSource, "czm_non_depth_clamp_main" ); modifiedFS += "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" + " czm_writeDepthClamp();\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"); 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"); } } 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, vertexCacheOptimize: primitive.vertexCacheOptimize, compressVertices: primitive.compressVertices, modelMatrix: primitive.modelMatrix, createPickOffsets: primitive._createPickOffsets, }, transferableObjects ), transferableObjects ); primitive._createGeometryResults = undefined; primitive._state = PrimitiveState.COMBINING; when(promise, function (packedResult) { var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult); primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; primitive.modelMatrix = Matrix4.clone( result.modelMatrix, primitive.modelMatrix ); primitive._pickOffsets = result.pickOffsets; primitive._offsetInstanceExtend = result.offsetInstanceExtend; primitive._instanceBoundingSpheres = result.boundingSpheres; primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._recomputeBoundingSpheres = true; primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); } }).otherwise(function (error) { setReady(primitive, frameState, PrimitiveState.FAILED, error); }); } } function loadSynchronous(primitive, frameState) { var instances = Array.isArray(primitive.geometryInstances) ? primitive.geometryInstances : [primitive.geometryInstances]; var length = (primitive._numberOfInstances = instances.length); var clonedInstances = new Array(length); var instanceIds = primitive._instanceIds; var instance; var i; var geometryIndex = 0; for (i = 0; i < length; i++) { instance = instances[i]; var geometry = instance.geometry; var createdGeometry; if (defined(geometry.attributes) && defined(geometry.primitiveType)) { createdGeometry = cloneGeometry(geometry); } else { createdGeometry = geometry.constructor.createGeometry(geometry); } clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); instanceIds.push(instance.id); } clonedInstances.length = geometryIndex; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var result = PrimitivePipeline.combineGeometry({ instances: clonedInstances, ellipsoid: projection.ellipsoid, projection: projection, elementIndexUintSupported: frameState.context.elementIndexUint, scene3DOnly: scene3DOnly, vertexCacheOptimize: primitive.vertexCacheOptimize, compressVertices: primitive.compressVertices, modelMatrix: primitive.modelMatrix, createPickOffsets: primitive._createPickOffsets, }); primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; primitive.modelMatrix = Matrix4.clone( result.modelMatrix, primitive.modelMatrix ); primitive._pickOffsets = result.pickOffsets; primitive._offsetInstanceExtend = result.offsetInstanceExtend; primitive._instanceBoundingSpheres = result.boundingSpheres; primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._recomputeBoundingSpheres = true; primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); } } function recomputeBoundingSpheres(primitive, frameState) { var offsetIndex = primitive._batchTableAttributeIndices.offset; if (!primitive._recomputeBoundingSpheres || !defined(offsetIndex)) { primitive._recomputeBoundingSpheres = false; return; } var i; var offsetInstanceExtend = primitive._offsetInstanceExtend; var boundingSpheres = primitive._instanceBoundingSpheres; var length = boundingSpheres.length; var newBoundingSpheres = primitive._tempBoundingSpheres; if (!defined(newBoundingSpheres)) { newBoundingSpheres = new Array(length); for (i = 0; i < length; i++) { newBoundingSpheres[i] = new BoundingSphere(); } primitive._tempBoundingSpheres = newBoundingSpheres; } for (i = 0; i < length; ++i) { var newBS = newBoundingSpheres[i]; var offset = primitive._batchTable.getBatchedAttribute( i, offsetIndex, new Cartesian3() ); newBS = boundingSpheres[i].clone(newBS); transformBoundingSphere(newBS, offset, offsetInstanceExtend[i]); } var combinedBS = []; var combinedWestBS = []; var combinedEastBS = []; for (i = 0; i < length; ++i) { var bs = newBoundingSpheres[i]; var minX = bs.center.x - bs.radius; if ( minX > 0 || BoundingSphere.intersectPlane(bs, Plane.ORIGIN_ZX_PLANE) !== Intersect.INTERSECTING ) { combinedBS.push(bs); } else { combinedWestBS.push(bs); combinedEastBS.push(bs); } } var resultBS1 = combinedBS[0]; var resultBS2 = combinedEastBS[0]; var resultBS3 = combinedWestBS[0]; for (i = 1; i < combinedBS.length; i++) { resultBS1 = BoundingSphere.union(resultBS1, combinedBS[i]); } for (i = 1; i < combinedEastBS.length; i++) { resultBS2 = BoundingSphere.union(resultBS2, combinedEastBS[i]); } for (i = 1; i < combinedWestBS.length; i++) { resultBS3 = BoundingSphere.union(resultBS3, combinedWestBS[i]); } var result = []; if (defined(resultBS1)) { result.push(resultBS1); } if (defined(resultBS2)) { result.push(resultBS2); } if (defined(resultBS3)) { result.push(resultBS3); } for (i = 0; i < result.length; i++) { var boundingSphere = result[i].clone(primitive._boundingSpheres[i]); primitive._boundingSpheres[i] = boundingSphere; primitive._boundingSphereCV[i] = BoundingSphere.projectTo2D( boundingSphere, frameState.mapProjection, primitive._boundingSphereCV[i] ); } Primitive._updateBoundingVolumes( primitive, frameState, primitive.modelMatrix, true ); primitive._recomputeBoundingSpheres = false; } var scratchBoundingSphereCenterEncoded = new EncodedCartesian3(); var scratchBoundingSphereCartographic = new Cartographic(); var scratchBoundingSphereCenter2D = new Cartesian3(); var scratchBoundingSphere = new BoundingSphere(); function updateBatchTableBoundingSpheres(primitive, frameState) { var hasDistanceDisplayCondition = de