UNPKG

@cesium/engine

Version:

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

1,485 lines (1,331 loc) 80.8 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 Frozen from "../Core/Frozen.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 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 * const 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 * const 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) * } * }); * const 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(), * asynchronous : false * })); * * @see GeometryInstance * @see Appearance * @see ClassificationPrimitive * @see GroundPrimitive */ function Primitive(options) { options = options ?? Frozen.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 * const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0); * p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin); */ this.modelMatrix = Matrix4.clone(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 = options.show ?? true; this._vertexCacheOptimize = options.vertexCacheOptimize ?? false; this._interleave = options.interleave ?? false; this._releaseGeometryInstances = options.releaseGeometryInstances ?? true; this._allowPicking = options.allowPicking ?? true; this._asynchronous = options.asynchronous ?? true; this._compressVertices = 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 = 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 = 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 = 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 = new Map(); 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._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 * * @example * // Wait for a primitive to become ready before accessing attributes * const removeListener = scene.postRender.addEventListener(() => { * if (!frustumPrimitive.ready) { * return; * } * * const attributes = primitive.getGeometryInstanceAttributes('an id'); * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA); * * removeListener(); * }); */ ready: { get: function () { return this._ready; }, }, }); function getCommonPerInstanceAttributeNames(instances) { const length = instances.length; const attributesInAllInstances = []; const attributes0 = instances[0].attributes; let name; for (name in attributes0) { if (attributes0.hasOwnProperty(name) && defined(attributes0[name])) { const attribute = attributes0[name]; let inAllInstances = true; // Does this same attribute exist in all instances? for (let i = 1; i < length; ++i) { const 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; } const scratchGetAttributeCartesian2 = new Cartesian2(); const scratchGetAttributeCartesian3 = new Cartesian3(); const scratchGetAttributeCartesian4 = new Cartesian4(); function getAttributeValue(value) { const 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) { const geometryInstances = primitive.geometryInstances; const instances = Array.isArray(geometryInstances) ? geometryInstances : [geometryInstances]; const numberOfInstances = instances.length; if (numberOfInstances === 0) { return; } const names = getCommonPerInstanceAttributeNames(instances); const length = names.length; const attributes = []; const attributeIndices = {}; const boundingSphereAttributeIndices = {}; let offset2DIndex; const firstInstance = instances[0]; let instanceAttributes = firstInstance.attributes; let i; let name; let 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, }); const attributesLength = attributes.length; const batchTable = new BatchTable(context, attributes, numberOfInstances); for (i = 0; i < numberOfInstances; ++i) { const instance = instances[i]; instanceAttributes = instance.attributes; for (let j = 0; j < length; ++j) { name = names[j]; attribute = instanceAttributes[name]; const value = getAttributeValue(attribute.value); const attributeIndex = attributeIndices[name]; batchTable.setBatchedAttribute(i, attributeIndex, value); } const pickObject = { primitive: instance.pickPrimitive ?? primitive, }; if (defined(instance.id)) { pickObject.id = instance.id; } const pickId = context.createPickId(pickObject); primitive._pickIds.push(pickId); const pickColor = pickId.color; const 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) { let 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) { const attributes = geometry.attributes; const newAttributes = new GeometryAttributes(); for (const property in attributes) { if (attributes.hasOwnProperty(property) && defined(attributes[property])) { newAttributes[property] = cloneAttribute(attributes[property]); } } let indices; if (defined(geometry.indices)) { const 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, }; } const positionRegex = /in\s+vec(?:3|4)\s+(.*)3DHigh;/g; Primitive._modifyShaderPosition = function ( primitive, vertexShaderSource, scene3DOnly, ) { let match; let forwardDecl = ""; let attributes = ""; let computeFunctions = ""; while ((match = positionRegex.exec(vertexShaderSource)) !== null) { const name = match[1]; const 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 += `in vec3 ${name}2DHigh;\nin 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( /in\s+vec(?:3|4)\s+position3DHigh;/g, "", ); vertexShaderSource = vertexShaderSource.replace( /in\s+vec(?:3|4)\s+position3DLow;/g, "", ); forwardDecl += "uniform mat4 u_modifiedModelView;\n"; attributes += "in 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; } const renamedVS = ShaderSource.replaceMain( vertexShaderSource, "czm_non_show_main", ); const 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(/in\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'); let modifiedVS = vertexShaderSource; modifiedVS = modifiedVS.replace(/in\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) { const renamedVS = ShaderSource.replaceMain(source, "czm_non_pick_main"); const pickMain = "out 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 `in vec4 v_pickColor;\n${source}`; } Primitive._updatePickColorAttribute = function (source) { let vsPick = source.replace(/in\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; } let attr = "in float batchId;\n"; attr += "in float applyOffset;"; let modifiedShader = vertexShaderSource.replace( /in\s+float\s+batchId;/g, attr, ); let 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; } const renamedVS = ShaderSource.replaceMain( vertexShaderSource, "czm_non_distanceDisplayCondition_main", ); let 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; } const containsNormal = vertexShaderSource.search(/in\s+vec3\s+normal;/g) !== -1; const containsSt = vertexShaderSource.search(/in\s+vec2\s+st;/g) !== -1; if (!containsNormal && !containsSt) { return vertexShaderSource; } const containsTangent = vertexShaderSource.search(/in\s+vec3\s+tangent;/g) !== -1; const containsBitangent = vertexShaderSource.search(/in\s+vec3\s+bitangent;/g) !== -1; let numComponents = containsSt && containsNormal ? 2.0 : 1.0; numComponents += containsTangent || containsBitangent ? 1 : 0; const type = numComponents > 1 ? `vec${numComponents}` : "float"; const attributeName = "compressedAttributes"; const attributeDecl = `in ${type} ${attributeName};`; let globalDecl = ""; let decode = ""; if (containsSt) { globalDecl += "vec2 st;\n"; const 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`; } } let modifiedVS = vertexShaderSource; modifiedVS = modifiedVS.replace(/in\s+vec3\s+normal;/g, ""); modifiedVS = modifiedVS.replace(/in\s+vec2\s+st;/g, ""); modifiedVS = modifiedVS.replace(/in\s+vec3\s+tangent;/g, ""); modifiedVS = modifiedVS.replace(/in\s+vec3\s+bitangent;/g, ""); modifiedVS = ShaderSource.replaceMain(modifiedVS, "czm_non_compressed_main"); const compressedMain = `${"void main() \n" + "{ \n"}${decode} czm_non_compressed_main(); \n` + `}`; return [attributeDecl, globalDecl, modifiedVS, compressedMain].join("\n"); } function depthClampVS(vertexShaderSource) { let 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) { let modifiedFS = ShaderSource.replaceMain( fragmentShaderSource, "czm_non_depth_clamp_main", ); modifiedFS += "void main() {\n" + " czm_non_depth_clamp_main();\n" + " #if defined(LOG_DEPTH)\n" + " czm_writeLogDepth();\n" + " #else\n" + " czm_writeDepthClamp();\n" + " #endif\n" + "}\n"; 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. const shaderAttributes = shaderProgram.vertexAttributes; //>>includeStart('debug', pragmas.debug); for (const 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]; }; } const numberOfCreationWorkers = Math.max( FeatureDetection.hardwareConcurrency - 1, 1, ); let createGeometryTaskProcessors; const combineGeometryTaskProcessor = new TaskProcessor("combineGeometry"); function loadAsynchronous(primitive, frameState) { let instances; let geometry; let i; let j; const instanceIds = primitive._instanceIds; if (primitive._state === PrimitiveState.READY) { instances = Array.isArray(primitive.geometryInstances) ? primitive.geometryInstances : [primitive.geometryInstances]; const length = (primitive._numberOfInstances = instances.length); const promises = []; let subTasks = []; for (i = 0; i < length; ++i) { geometry = instances[i].geometry; instanceIds.push(instances[i].id); //>>includeStart('debug', pragmas.debug); if ( (defined(geometry._workerName) && defined(geometry._workerPath)) || (!defined(geometry._workerName) && !defined(geometry._workerPath)) ) { throw new DeveloperError( "Must define either _workerName or _workerPath for asynchronous geometry.", ); } //>>includeEnd('debug'); subTasks.push({ moduleName: geometry._workerName, modulePath: geometry._workerPath, geometry: geometry, }); } if (!defined(createGeometryTaskProcessors)) { createGeometryTaskProcessors = new Array(numberOfCreationWorkers); for (i = 0; i < numberOfCreationWorkers; i++) { createGeometryTaskProcessors[i] = new TaskProcessor("createGeometry"); } } let subTask; subTasks = subdivideArray(subTasks, numberOfCreationWorkers); for (i = 0; i < subTasks.length; i++) { let packedLength = 0; const workerSubTasks = subTasks[i]; const workerSubTasksLength = workerSubTasks.length; for (j = 0; j < workerSubTasksLength; ++j) { subTask = workerSubTasks[j]; geometry = subTask.geometry; if (defined(geometry.constructor.pack)) { subTask.offset = packedLength; packedLength += geometry.constructor.packedLength ?? geometry.packedLength; } } let subTaskTransferableObjects; if (packedLength > 0) { const 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; Promise.all(promises) .then(function (results) { primitive._createGeometryResults = results; primitive._state = PrimitiveState.CREATED; }) .catch(function (error) { setReady(primitive, frameState, PrimitiveState.FAILED, error); }); } else if (primitive._state === PrimitiveState.CREATED) { const transferableObjects = []; instances = Array.isArray(primitive.geometryInstances) ? primitive.geometryInstances : [primitive.geometryInstances]; const scene3DOnly = frameState.scene3DOnly; const projection = frameState.mapProjection; const 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; Promise.resolve(promise) .then(function (packedResult) { const 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); } }) .catch(function (error) { setReady(primitive, frameState, PrimitiveState.FAILED, error); }); } } function loadSynchronous(primitive, frameState) { const instances = Array.isArray(primitive.geometryInstances) ? primitive.geometryInstances : [primitive.geometryInstances]; const length = (primitive._numberOfInstances = instances.length); const clonedInstances = new Array(length); const instanceIds = primitive._instanceIds; let instance; let i; let geometryIndex = 0; for (i = 0; i < length; i++) { instance = instances[i]; const geometry = instance.geometry; let 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; const scene3DOnly = frameState.scene3DOnly; const projection = frameState.mapProjection; const 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) { const offsetIndex = primitive._batchTableAttributeIndices.offset; if (!primitive._recomputeBoundingSpheres || !defined(offsetIndex)) { primitive._recomputeBoundingSpheres = false; return; } let i; const offsetInstanceExtend = primitive._offsetInstanceExtend; const boundingSpheres = primitive._instanceBoundingSpheres; const length = boundingSpheres.length; let 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) { let newBS = newBoundingSpheres[i]; const offset = primitive._batchTable.getBatchedAttribute( i, offsetIndex, new Cartesian3(), ); newBS = boundingSpheres[i].clone(newBS); transformBoundingSphere(newBS, offset, offsetInstanceExtend[i]); } const combinedBS = []; const combinedWestBS = []; const combinedEastBS = []; for (i = 0; i < length; ++i) { const bs = newBoundingSpheres[i]; const 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); } } let resultBS1 = combinedBS[0]; let resultBS2 = combinedEastBS[0]; let 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]); } const 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++) { const 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; } const scratchBoundingSphereCenterEncoded = new EncodedCartesian3(); const scratchBoundingSphereCartographic = new Cartographic(); const scratchBoundingSphereCenter2D = new Cartesian3(); const scratchBoundingSphere = new BoundingSphere(); function updateBatchTableBoundingSpheres(primitive, frameState) { const hasDistanceDisplayCondition = defined( primitive._batchTableAttributeIndices.distanceDisplayCondition, ); if ( !hasDistanceDisplayCondition || primitive._batchTableBoundingSpheresUpdated ) { return; } const indices = primitive._batchTableBoundingSphereAttributeIndices; const center3DHighIndex = indices.center3DHigh; const center3DLowIndex = indices.center3DLow; const center2DHighIndex = indices.center2DHigh; const center2DLowIndex = indices.center2DLow; const radiusIndex = indices.