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
JavaScript
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