You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2566 lines
80 KiB
JavaScript
2566 lines
80 KiB
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"
|
|
);
|
|
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 = defined(
|
|
primitive._batchTableAttributeIndices.distanceDisplayCondition
|
|
);
|
|
if (
|
|
!hasDistanceDisplayCondition ||
|
|
primitive._batchTableBoundingSpheresUpdated
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var indices = primitive._batchTableBoundingSphereAttributeIndices;
|
|
var center3DHighIndex = indices.center3DHigh;
|
|
var center3DLowIndex = indices.center3DLow;
|
|
var center2DHighIndex = indices.center2DHigh;
|
|
var center2DLowIndex = indices.center2DLow;
|
|
var radiusIndex = indices.radius;
|
|
|
|
var projection = frameState.mapProjection;
|
|
var ellipsoid = projection.ellipsoid;
|
|
|
|
var batchTable = primitive._batchTable;
|
|
var boundingSpheres = primitive._instanceBoundingSpheres;
|
|
var length = boundingSpheres.length;
|
|
|
|
for (var i = 0; i < length; ++i) {
|
|
var boundingSphere = boundingSpheres[i];
|
|
if (!defined(boundingSphere)) {
|
|
continue;
|
|
}
|
|
|
|
var modelMatrix = primitive.modelMatrix;
|
|
if (defined(modelMatrix)) {
|
|
boundingSphere = BoundingSphere.transform(
|
|
boundingSphere,
|
|
modelMatrix,
|
|
scratchBoundingSphere
|
|
);
|
|
}
|
|
|
|
var center = boundingSphere.center;
|
|
var radius = boundingSphere.radius;
|
|
|
|
var encodedCenter = EncodedCartesian3.fromCartesian(
|
|
center,
|
|
scratchBoundingSphereCenterEncoded
|
|
);
|
|
batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high);
|
|
batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low);
|
|
|
|
if (!frameState.scene3DOnly) {
|
|
var cartographic = ellipsoid.cartesianToCartographic(
|
|
center,
|
|
scratchBoundingSphereCartographic
|
|
);
|
|
var center2D = projection.project(
|
|
cartographic,
|
|
scratchBoundingSphereCenter2D
|
|
);
|
|
encodedCenter = EncodedCartesian3.fromCartesian(
|
|
center2D,
|
|
scratchBoundingSphereCenterEncoded
|
|
);
|
|
batchTable.setBatchedAttribute(i, center2DHighIndex, encodedCenter.high);
|
|
batchTable.setBatchedAttribute(i, center2DLowIndex, encodedCenter.low);
|
|
}
|
|
|
|
batchTable.setBatchedAttribute(i, radiusIndex, radius);
|
|
}
|
|
|
|
primitive._batchTableBoundingSpheresUpdated = true;
|
|
}
|
|
|
|
var offsetScratchCartesian = new Cartesian3();
|
|
var offsetCenterScratch = new Cartesian3();
|
|
function updateBatchTableOffsets(primitive, frameState) {
|
|
var hasOffset = defined(primitive._batchTableAttributeIndices.offset);
|
|
if (
|
|
!hasOffset ||
|
|
primitive._batchTableOffsetsUpdated ||
|
|
frameState.scene3DOnly
|
|
) {
|
|
return;
|
|
}
|
|
|
|
var index2D = primitive._batchTableOffsetAttribute2DIndex;
|
|
|
|
var projection = frameState.mapProjection;
|
|
var ellipsoid = projection.ellipsoid;
|
|
|
|
var batchTable = primitive._batchTable;
|
|
var boundingSpheres = primitive._instanceBoundingSpheres;
|
|
var length = boundingSpheres.length;
|
|
|
|
for (var i = 0; i < length; ++i) {
|
|
var boundingSphere = boundingSpheres[i];
|
|
if (!defined(boundingSphere)) {
|
|
continue;
|
|
}
|
|
var offset = batchTable.getBatchedAttribute(
|
|
i,
|
|
primitive._batchTableAttributeIndices.offset
|
|
);
|
|
if (Cartesian3.equals(offset, Cartesian3.ZERO)) {
|
|
batchTable.setBatchedAttribute(i, index2D, Cartesian3.ZERO);
|
|
continue;
|
|
}
|
|
|
|
var modelMatrix = primitive.modelMatrix;
|
|
if (defined(modelMatrix)) {
|
|
boundingSphere = BoundingSphere.transform(
|
|
boundingSphere,
|
|
modelMatrix,
|
|
scratchBoundingSphere
|
|
);
|
|
}
|
|
|
|
var center = boundingSphere.center;
|
|
center = ellipsoid.scaleToGeodeticSurface(center, offsetCenterScratch);
|
|
var cartographic = ellipsoid.cartesianToCartographic(
|
|
center,
|
|
scratchBoundingSphereCartographic
|
|
);
|
|
var center2D = projection.project(
|
|
cartographic,
|
|
scratchBoundingSphereCenter2D
|
|
);
|
|
|
|
var newPoint = Cartesian3.add(offset, center, offsetScratchCartesian);
|
|
cartographic = ellipsoid.cartesianToCartographic(newPoint, cartographic);
|
|
|
|
var newPointProjected = projection.project(
|
|
cartographic,
|
|
offsetScratchCartesian
|
|
);
|
|
|
|
var newVector = Cartesian3.subtract(
|
|
newPointProjected,
|
|
center2D,
|
|
offsetScratchCartesian
|
|
);
|
|
|
|
var x = newVector.x;
|
|
newVector.x = newVector.z;
|
|
newVector.z = newVector.y;
|
|
newVector.y = x;
|
|
|
|
batchTable.setBatchedAttribute(i, index2D, newVector);
|
|
}
|
|
|
|
primitive._batchTableOffsetsUpdated = true;
|
|
}
|
|
|
|
function createVertexArray(primitive, frameState) {
|
|
var attributeLocations = primitive._attributeLocations;
|
|
var geometries = primitive._geometries;
|
|
var scene3DOnly = frameState.scene3DOnly;
|
|
var context = frameState.context;
|
|
|
|
var va = [];
|
|
var length = geometries.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
var geometry = geometries[i];
|
|
|
|
va.push(
|
|
VertexArray.fromGeometry({
|
|
context: context,
|
|
geometry: geometry,
|
|
attributeLocations: attributeLocations,
|
|
bufferUsage: BufferUsage.STATIC_DRAW,
|
|
interleave: primitive._interleave,
|
|
})
|
|
);
|
|
|
|
if (defined(primitive._createBoundingVolumeFunction)) {
|
|
primitive._createBoundingVolumeFunction(frameState, geometry);
|
|
} else {
|
|
primitive._boundingSpheres.push(
|
|
BoundingSphere.clone(geometry.boundingSphere)
|
|
);
|
|
primitive._boundingSphereWC.push(new BoundingSphere());
|
|
|
|
if (!scene3DOnly) {
|
|
var center = geometry.boundingSphereCV.center;
|
|
var x = center.x;
|
|
var y = center.y;
|
|
var z = center.z;
|
|
center.x = z;
|
|
center.y = x;
|
|
center.z = y;
|
|
|
|
primitive._boundingSphereCV.push(
|
|
BoundingSphere.clone(geometry.boundingSphereCV)
|
|
);
|
|
primitive._boundingSphere2D.push(new BoundingSphere());
|
|
primitive._boundingSphereMorph.push(new BoundingSphere());
|
|
}
|
|
}
|
|
}
|
|
|
|
primitive._va = va;
|
|
primitive._primitiveType = geometries[0].primitiveType;
|
|
|
|
if (primitive.releaseGeometryInstances) {
|
|
primitive.geometryInstances = undefined;
|
|
}
|
|
|
|
primitive._geometries = undefined;
|
|
setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined);
|
|
}
|
|
|
|
function createRenderStates(primitive, context, appearance, twoPasses) {
|
|
var renderState = appearance.getRenderState();
|
|
var rs;
|
|
|
|
if (twoPasses) {
|
|
rs = clone(renderState, false);
|
|
rs.cull = {
|
|
enabled: true,
|
|
face: CullFace.BACK,
|
|
};
|
|
primitive._frontFaceRS = RenderState.fromCache(rs);
|
|
|
|
rs.cull.face = CullFace.FRONT;
|
|
primitive._backFaceRS = RenderState.fromCache(rs);
|
|
} else {
|
|
primitive._frontFaceRS = RenderState.fromCache(renderState);
|
|
primitive._backFaceRS = primitive._frontFaceRS;
|
|
}
|
|
|
|
rs = clone(renderState, false);
|
|
if (defined(primitive._depthFailAppearance)) {
|
|
rs.depthTest.enabled = false;
|
|
}
|
|
|
|
if (defined(primitive._depthFailAppearance)) {
|
|
renderState = primitive._depthFailAppearance.getRenderState();
|
|
rs = clone(renderState, false);
|
|
rs.depthTest.func = DepthFunction.GREATER;
|
|
if (twoPasses) {
|
|
rs.cull = {
|
|
enabled: true,
|
|
face: CullFace.BACK,
|
|
};
|
|
primitive._frontFaceDepthFailRS = RenderState.fromCache(rs);
|
|
|
|
rs.cull.face = CullFace.FRONT;
|
|
primitive._backFaceDepthFailRS = RenderState.fromCache(rs);
|
|
} else {
|
|
primitive._frontFaceDepthFailRS = RenderState.fromCache(rs);
|
|
primitive._backFaceDepthFailRS = primitive._frontFaceRS;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createShaderProgram(primitive, frameState, appearance) {
|
|
var context = frameState.context;
|
|
|
|
var attributeLocations = primitive._attributeLocations;
|
|
|
|
var vs = primitive._batchTable.getVertexShaderCallback()(
|
|
appearance.vertexShaderSource
|
|
);
|
|
vs = Primitive._appendOffsetToShader(primitive, vs);
|
|
vs = Primitive._appendShowToShader(primitive, vs);
|
|
vs = Primitive._appendDistanceDisplayConditionToShader(
|
|
primitive,
|
|
vs,
|
|
frameState.scene3DOnly
|
|
);
|
|
vs = appendPickToVertexShader(vs);
|
|
vs = Primitive._updateColorAttribute(primitive, vs, false);
|
|
vs = modifyForEncodedNormals(primitive, vs);
|
|
vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
|
|
var fs = appearance.getFragmentShaderSource();
|
|
fs = appendPickToFragmentShader(fs);
|
|
|
|
primitive._sp = ShaderProgram.replaceCache({
|
|
context: context,
|
|
shaderProgram: primitive._sp,
|
|
vertexShaderSource: vs,
|
|
fragmentShaderSource: fs,
|
|
attributeLocations: attributeLocations,
|
|
});
|
|
validateShaderMatching(primitive._sp, attributeLocations);
|
|
|
|
if (defined(primitive._depthFailAppearance)) {
|
|
vs = primitive._batchTable.getVertexShaderCallback()(
|
|
primitive._depthFailAppearance.vertexShaderSource
|
|
);
|
|
vs = Primitive._appendShowToShader(primitive, vs);
|
|
vs = Primitive._appendDistanceDisplayConditionToShader(
|
|
primitive,
|
|
vs,
|
|
frameState.scene3DOnly
|
|
);
|
|
vs = appendPickToVertexShader(vs);
|
|
vs = Primitive._updateColorAttribute(primitive, vs, true);
|
|
vs = modifyForEncodedNormals(primitive, vs);
|
|
vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
|
|
vs = depthClampVS(vs);
|
|
|
|
fs = primitive._depthFailAppearance.getFragmentShaderSource();
|
|
fs = appendPickToFragmentShader(fs);
|
|
fs = depthClampFS(fs);
|
|
|
|
primitive._spDepthFail = ShaderProgram.replaceCache({
|
|
context: context,
|
|
shaderProgram: primitive._spDepthFail,
|
|
vertexShaderSource: vs,
|
|
fragmentShaderSource: fs,
|
|
attributeLocations: attributeLocations,
|
|
});
|
|
validateShaderMatching(primitive._spDepthFail, attributeLocations);
|
|
}
|
|
}
|
|
|
|
var modifiedModelViewScratch = new Matrix4();
|
|
var rtcScratch = new Cartesian3();
|
|
|
|
function getUniforms(primitive, appearance, material, frameState) {
|
|
// Create uniform map by combining uniforms from the appearance and material if either have uniforms.
|
|
var materialUniformMap = defined(material) ? material._uniforms : undefined;
|
|
var appearanceUniformMap = {};
|
|
var appearanceUniforms = appearance.uniforms;
|
|
if (defined(appearanceUniforms)) {
|
|
// Convert to uniform map of functions for the renderer
|
|
for (var name in appearanceUniforms) {
|
|
if (appearanceUniforms.hasOwnProperty(name)) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (defined(materialUniformMap) && defined(materialUniformMap[name])) {
|
|
// Later, we could rename uniforms behind-the-scenes if needed.
|
|
throw new DeveloperError(
|
|
"Appearance and material have a uniform with the same name: " + name
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
appearanceUniformMap[name] = getUniformFunction(
|
|
appearanceUniforms,
|
|
name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
var uniforms = combine(appearanceUniformMap, materialUniformMap);
|
|
uniforms = primitive._batchTable.getUniformMapCallback()(uniforms);
|
|
|
|
if (defined(primitive.rtcCenter)) {
|
|
uniforms.u_modifiedModelView = function () {
|
|
var viewMatrix = frameState.context.uniformState.view;
|
|
Matrix4.multiply(
|
|
viewMatrix,
|
|
primitive._modelMatrix,
|
|
modifiedModelViewScratch
|
|
);
|
|
Matrix4.multiplyByPoint(
|
|
modifiedModelViewScratch,
|
|
primitive.rtcCenter,
|
|
rtcScratch
|
|
);
|
|
Matrix4.setTranslation(
|
|
modifiedModelViewScratch,
|
|
rtcScratch,
|
|
modifiedModelViewScratch
|
|
);
|
|
return modifiedModelViewScratch;
|
|
};
|
|
}
|
|
|
|
return uniforms;
|
|
}
|
|
|
|
function createCommands(
|
|
primitive,
|
|
appearance,
|
|
material,
|
|
translucent,
|
|
twoPasses,
|
|
colorCommands,
|
|
pickCommands,
|
|
frameState
|
|
) {
|
|
var uniforms = getUniforms(primitive, appearance, material, frameState);
|
|
|
|
var depthFailUniforms;
|
|
if (defined(primitive._depthFailAppearance)) {
|
|
depthFailUniforms = getUniforms(
|
|
primitive,
|
|
primitive._depthFailAppearance,
|
|
primitive._depthFailAppearance.material,
|
|
frameState
|
|
);
|
|
}
|
|
|
|
var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
|
|
|
|
var multiplier = twoPasses ? 2 : 1;
|
|
multiplier *= defined(primitive._depthFailAppearance) ? 2 : 1;
|
|
colorCommands.length = primitive._va.length * multiplier;
|
|
|
|
var length = colorCommands.length;
|
|
var vaIndex = 0;
|
|
for (var i = 0; i < length; ++i) {
|
|
var colorCommand;
|
|
|
|
if (twoPasses) {
|
|
colorCommand = colorCommands[i];
|
|
if (!defined(colorCommand)) {
|
|
colorCommand = colorCommands[i] = new DrawCommand({
|
|
owner: primitive,
|
|
primitiveType: primitive._primitiveType,
|
|
});
|
|
}
|
|
colorCommand.vertexArray = primitive._va[vaIndex];
|
|
colorCommand.renderState = primitive._backFaceRS;
|
|
colorCommand.shaderProgram = primitive._sp;
|
|
colorCommand.uniformMap = uniforms;
|
|
colorCommand.pass = pass;
|
|
|
|
++i;
|
|
}
|
|
|
|
colorCommand = colorCommands[i];
|
|
if (!defined(colorCommand)) {
|
|
colorCommand = colorCommands[i] = new DrawCommand({
|
|
owner: primitive,
|
|
primitiveType: primitive._primitiveType,
|
|
});
|
|
}
|
|
colorCommand.vertexArray = primitive._va[vaIndex];
|
|
colorCommand.renderState = primitive._frontFaceRS;
|
|
colorCommand.shaderProgram = primitive._sp;
|
|
colorCommand.uniformMap = uniforms;
|
|
colorCommand.pass = pass;
|
|
|
|
if (defined(primitive._depthFailAppearance)) {
|
|
if (twoPasses) {
|
|
++i;
|
|
|
|
colorCommand = colorCommands[i];
|
|
if (!defined(colorCommand)) {
|
|
colorCommand = colorCommands[i] = new DrawCommand({
|
|
owner: primitive,
|
|
primitiveType: primitive._primitiveType,
|
|
});
|
|
}
|
|
colorCommand.vertexArray = primitive._va[vaIndex];
|
|
colorCommand.renderState = primitive._backFaceDepthFailRS;
|
|
colorCommand.shaderProgram = primitive._spDepthFail;
|
|
colorCommand.uniformMap = depthFailUniforms;
|
|
colorCommand.pass = pass;
|
|
}
|
|
|
|
++i;
|
|
|
|
colorCommand = colorCommands[i];
|
|
if (!defined(colorCommand)) {
|
|
colorCommand = colorCommands[i] = new DrawCommand({
|
|
owner: primitive,
|
|
primitiveType: primitive._primitiveType,
|
|
});
|
|
}
|
|
colorCommand.vertexArray = primitive._va[vaIndex];
|
|
colorCommand.renderState = primitive._frontFaceDepthFailRS;
|
|
colorCommand.shaderProgram = primitive._spDepthFail;
|
|
colorCommand.uniformMap = depthFailUniforms;
|
|
colorCommand.pass = pass;
|
|
}
|
|
|
|
++vaIndex;
|
|
}
|
|
}
|
|
|
|
Primitive._updateBoundingVolumes = function (
|
|
primitive,
|
|
frameState,
|
|
modelMatrix,
|
|
forceUpdate
|
|
) {
|
|
var i;
|
|
var length;
|
|
var boundingSphere;
|
|
|
|
if (forceUpdate || !Matrix4.equals(modelMatrix, primitive._modelMatrix)) {
|
|
Matrix4.clone(modelMatrix, primitive._modelMatrix);
|
|
length = primitive._boundingSpheres.length;
|
|
for (i = 0; i < length; ++i) {
|
|
boundingSphere = primitive._boundingSpheres[i];
|
|
if (defined(boundingSphere)) {
|
|
primitive._boundingSphereWC[i] = BoundingSphere.transform(
|
|
boundingSphere,
|
|
modelMatrix,
|
|
primitive._boundingSphereWC[i]
|
|
);
|
|
if (!frameState.scene3DOnly) {
|
|
primitive._boundingSphere2D[i] = BoundingSphere.clone(
|
|
primitive._boundingSphereCV[i],
|
|
primitive._boundingSphere2D[i]
|
|
);
|
|
primitive._boundingSphere2D[i].center.x = 0.0;
|
|
primitive._boundingSphereMorph[i] = BoundingSphere.union(
|
|
primitive._boundingSphereWC[i],
|
|
primitive._boundingSphereCV[i]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update bounding volumes for primitives that are sized in pixels.
|
|
// The pixel size in meters varies based on the distance from the camera.
|
|
var pixelSize = primitive.appearance.pixelSize;
|
|
if (defined(pixelSize)) {
|
|
length = primitive._boundingSpheres.length;
|
|
for (i = 0; i < length; ++i) {
|
|
boundingSphere = primitive._boundingSpheres[i];
|
|
var boundingSphereWC = primitive._boundingSphereWC[i];
|
|
var pixelSizeInMeters = frameState.camera.getPixelSize(
|
|
boundingSphere,
|
|
frameState.context.drawingBufferWidth,
|
|
frameState.context.drawingBufferHeight
|
|
);
|
|
var sizeInMeters = pixelSizeInMeters * pixelSize;
|
|
boundingSphereWC.radius = boundingSphere.radius + sizeInMeters;
|
|
}
|
|
}
|
|
};
|
|
|
|
function updateAndQueueCommands(
|
|
primitive,
|
|
frameState,
|
|
colorCommands,
|
|
pickCommands,
|
|
modelMatrix,
|
|
cull,
|
|
debugShowBoundingVolume,
|
|
twoPasses
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (
|
|
frameState.mode !== SceneMode.SCENE3D &&
|
|
!Matrix4.equals(modelMatrix, Matrix4.IDENTITY)
|
|
) {
|
|
throw new DeveloperError(
|
|
"Primitive.modelMatrix is only supported in 3D mode."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix);
|
|
|
|
var boundingSpheres;
|
|
if (frameState.mode === SceneMode.SCENE3D) {
|
|
boundingSpheres = primitive._boundingSphereWC;
|
|
} else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
|
|
boundingSpheres = primitive._boundingSphereCV;
|
|
} else if (
|
|
frameState.mode === SceneMode.SCENE2D &&
|
|
defined(primitive._boundingSphere2D)
|
|
) {
|
|
boundingSpheres = primitive._boundingSphere2D;
|
|
} else if (defined(primitive._boundingSphereMorph)) {
|
|
boundingSpheres = primitive._boundingSphereMorph;
|
|
}
|
|
|
|
var commandList = frameState.commandList;
|
|
var passes = frameState.passes;
|
|
if (passes.render || passes.pick) {
|
|
var allowPicking = primitive.allowPicking;
|
|
var castShadows = ShadowMode.castShadows(primitive.shadows);
|
|
var receiveShadows = ShadowMode.receiveShadows(primitive.shadows);
|
|
var colorLength = colorCommands.length;
|
|
|
|
var factor = twoPasses ? 2 : 1;
|
|
factor *= defined(primitive._depthFailAppearance) ? 2 : 1;
|
|
|
|
for (var j = 0; j < colorLength; ++j) {
|
|
var sphereIndex = Math.floor(j / factor);
|
|
var colorCommand = colorCommands[j];
|
|
colorCommand.modelMatrix = modelMatrix;
|
|
colorCommand.boundingVolume = boundingSpheres[sphereIndex];
|
|
colorCommand.cull = cull;
|
|
colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
|
|
colorCommand.castShadows = castShadows;
|
|
colorCommand.receiveShadows = receiveShadows;
|
|
|
|
if (allowPicking) {
|
|
colorCommand.pickId = "v_pickColor";
|
|
} else {
|
|
colorCommand.pickId = undefined;
|
|
}
|
|
|
|
commandList.push(colorCommand);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
|
|
* get the draw commands needed to render this primitive.
|
|
* <p>
|
|
* Do not call this function directly. This is documented just to
|
|
* list the exceptions that may be propagated when the scene is rendered:
|
|
* </p>
|
|
*
|
|
* @exception {DeveloperError} All instance geometries must have the same primitiveType.
|
|
* @exception {DeveloperError} Appearance and material have a uniform with the same name.
|
|
* @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode.
|
|
* @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
|
|
*/
|
|
Primitive.prototype.update = function (frameState) {
|
|
if (
|
|
(!defined(this.geometryInstances) && this._va.length === 0) ||
|
|
(defined(this.geometryInstances) &&
|
|
Array.isArray(this.geometryInstances) &&
|
|
this.geometryInstances.length === 0) ||
|
|
!defined(this.appearance) ||
|
|
(frameState.mode !== SceneMode.SCENE3D && frameState.scene3DOnly) ||
|
|
(!frameState.passes.render && !frameState.passes.pick)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (defined(this._error)) {
|
|
throw this._error;
|
|
}
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (defined(this.rtcCenter) && !frameState.scene3DOnly) {
|
|
throw new DeveloperError(
|
|
"RTC rendering is only available for 3D only scenes."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
if (this._state === PrimitiveState.FAILED) {
|
|
return;
|
|
}
|
|
|
|
var context = frameState.context;
|
|
if (!defined(this._batchTable)) {
|
|
createBatchTable(this, context);
|
|
}
|
|
if (this._batchTable.attributes.length > 0) {
|
|
if (ContextLimits.maximumVertexTextureImageUnits === 0) {
|
|
throw new RuntimeError(
|
|
"Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero."
|
|
);
|
|
}
|
|
this._batchTable.update(frameState);
|
|
}
|
|
|
|
if (
|
|
this._state !== PrimitiveState.COMPLETE &&
|
|
this._state !== PrimitiveState.COMBINED
|
|
) {
|
|
if (this.asynchronous) {
|
|
loadAsynchronous(this, frameState);
|
|
} else {
|
|
loadSynchronous(this, frameState);
|
|
}
|
|
}
|
|
|
|
if (this._state === PrimitiveState.COMBINED) {
|
|
updateBatchTableBoundingSpheres(this, frameState);
|
|
updateBatchTableOffsets(this, frameState);
|
|
createVertexArray(this, frameState);
|
|
}
|
|
|
|
if (!this.show || this._state !== PrimitiveState.COMPLETE) {
|
|
return;
|
|
}
|
|
|
|
if (!this._batchTableOffsetsUpdated) {
|
|
updateBatchTableOffsets(this, frameState);
|
|
}
|
|
if (this._recomputeBoundingSpheres) {
|
|
recomputeBoundingSpheres(this, frameState);
|
|
}
|
|
|
|
// Create or recreate render state and shader program if appearance/material changed
|
|
var appearance = this.appearance;
|
|
var material = appearance.material;
|
|
var createRS = false;
|
|
var createSP = false;
|
|
|
|
if (this._appearance !== appearance) {
|
|
this._appearance = appearance;
|
|
this._material = material;
|
|
createRS = true;
|
|
createSP = true;
|
|
} else if (this._material !== material) {
|
|
this._material = material;
|
|
createSP = true;
|
|
}
|
|
|
|
var depthFailAppearance = this.depthFailAppearance;
|
|
var depthFailMaterial = defined(depthFailAppearance)
|
|
? depthFailAppearance.material
|
|
: undefined;
|
|
|
|
if (this._depthFailAppearance !== depthFailAppearance) {
|
|
this._depthFailAppearance = depthFailAppearance;
|
|
this._depthFailMaterial = depthFailMaterial;
|
|
createRS = true;
|
|
createSP = true;
|
|
} else if (this._depthFailMaterial !== depthFailMaterial) {
|
|
this._depthFailMaterial = depthFailMaterial;
|
|
createSP = true;
|
|
}
|
|
|
|
var translucent = this._appearance.isTranslucent();
|
|
if (this._translucent !== translucent) {
|
|
this._translucent = translucent;
|
|
createRS = true;
|
|
}
|
|
|
|
if (defined(this._material)) {
|
|
this._material.update(context);
|
|
}
|
|
|
|
var twoPasses = appearance.closed && translucent;
|
|
|
|
if (createRS) {
|
|
var rsFunc = defaultValue(
|
|
this._createRenderStatesFunction,
|
|
createRenderStates
|
|
);
|
|
rsFunc(this, context, appearance, twoPasses);
|
|
}
|
|
|
|
if (createSP) {
|
|
var spFunc = defaultValue(
|
|
this._createShaderProgramFunction,
|
|
createShaderProgram
|
|
);
|
|
spFunc(this, frameState, appearance);
|
|
}
|
|
|
|
if (createRS || createSP) {
|
|
var commandFunc = defaultValue(
|
|
this._createCommandsFunction,
|
|
createCommands
|
|
);
|
|
commandFunc(
|
|
this,
|
|
appearance,
|
|
material,
|
|
translucent,
|
|
twoPasses,
|
|
this._colorCommands,
|
|
this._pickCommands,
|
|
frameState
|
|
);
|
|
}
|
|
|
|
var updateAndQueueCommandsFunc = defaultValue(
|
|
this._updateAndQueueCommandsFunction,
|
|
updateAndQueueCommands
|
|
);
|
|
updateAndQueueCommandsFunc(
|
|
this,
|
|
frameState,
|
|
this._colorCommands,
|
|
this._pickCommands,
|
|
this.modelMatrix,
|
|
this.cull,
|
|
this.debugShowBoundingVolume,
|
|
twoPasses
|
|
);
|
|
};
|
|
|
|
var offsetBoundingSphereScratch1 = new BoundingSphere();
|
|
var offsetBoundingSphereScratch2 = new BoundingSphere();
|
|
function transformBoundingSphere(boundingSphere, offset, offsetAttribute) {
|
|
if (offsetAttribute === GeometryOffsetAttribute.TOP) {
|
|
var origBS = BoundingSphere.clone(
|
|
boundingSphere,
|
|
offsetBoundingSphereScratch1
|
|
);
|
|
var offsetBS = BoundingSphere.clone(
|
|
boundingSphere,
|
|
offsetBoundingSphereScratch2
|
|
);
|
|
offsetBS.center = Cartesian3.add(offsetBS.center, offset, offsetBS.center);
|
|
boundingSphere = BoundingSphere.union(origBS, offsetBS, boundingSphere);
|
|
} else if (offsetAttribute === GeometryOffsetAttribute.ALL) {
|
|
boundingSphere.center = Cartesian3.add(
|
|
boundingSphere.center,
|
|
offset,
|
|
boundingSphere.center
|
|
);
|
|
}
|
|
|
|
return boundingSphere;
|
|
}
|
|
|
|
function createGetFunction(batchTable, instanceIndex, attributeIndex) {
|
|
return function () {
|
|
var attributeValue = batchTable.getBatchedAttribute(
|
|
instanceIndex,
|
|
attributeIndex
|
|
);
|
|
var attribute = batchTable.attributes[attributeIndex];
|
|
var componentsPerAttribute = attribute.componentsPerAttribute;
|
|
var value = ComponentDatatype.createTypedArray(
|
|
attribute.componentDatatype,
|
|
componentsPerAttribute
|
|
);
|
|
if (defined(attributeValue.constructor.pack)) {
|
|
attributeValue.constructor.pack(attributeValue, value, 0);
|
|
} else {
|
|
value[0] = attributeValue;
|
|
}
|
|
return value;
|
|
};
|
|
}
|
|
|
|
function createSetFunction(
|
|
batchTable,
|
|
instanceIndex,
|
|
attributeIndex,
|
|
primitive,
|
|
name
|
|
) {
|
|
return function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (
|
|
!defined(value) ||
|
|
!defined(value.length) ||
|
|
value.length < 1 ||
|
|
value.length > 4
|
|
) {
|
|
throw new DeveloperError(
|
|
"value must be and array with length between 1 and 4."
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
var attributeValue = getAttributeValue(value);
|
|
batchTable.setBatchedAttribute(
|
|
instanceIndex,
|
|
attributeIndex,
|
|
attributeValue
|
|
);
|
|
if (name === "offset") {
|
|
primitive._recomputeBoundingSpheres = true;
|
|
primitive._batchTableOffsetsUpdated = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
var offsetScratch = new Cartesian3();
|
|
|
|
function createBoundingSphereProperties(primitive, properties, index) {
|
|
properties.boundingSphere = {
|
|
get: function () {
|
|
var boundingSphere = primitive._instanceBoundingSpheres[index];
|
|
if (defined(boundingSphere)) {
|
|
boundingSphere = boundingSphere.clone();
|
|
var modelMatrix = primitive.modelMatrix;
|
|
var offset = properties.offset;
|
|
if (defined(offset)) {
|
|
transformBoundingSphere(
|
|
boundingSphere,
|
|
Cartesian3.fromArray(offset.get(), 0, offsetScratch),
|
|
primitive._offsetInstanceExtend[index]
|
|
);
|
|
}
|
|
if (defined(modelMatrix)) {
|
|
boundingSphere = BoundingSphere.transform(
|
|
boundingSphere,
|
|
modelMatrix
|
|
);
|
|
}
|
|
}
|
|
|
|
return boundingSphere;
|
|
},
|
|
};
|
|
properties.boundingSphereCV = {
|
|
get: function () {
|
|
return primitive._instanceBoundingSpheresCV[index];
|
|
},
|
|
};
|
|
}
|
|
|
|
function createPickIdProperty(primitive, properties, index) {
|
|
properties.pickId = {
|
|
get: function () {
|
|
return primitive._pickIds[index];
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
|
|
*
|
|
* @param {*} id The id of the {@link GeometryInstance}.
|
|
* @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
|
|
*
|
|
* @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
|
|
*
|
|
* @example
|
|
* var attributes = primitive.getGeometryInstanceAttributes('an id');
|
|
* attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
|
|
* attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
|
|
* attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(100.0, 10000.0);
|
|
* attributes.offset = Cesium.OffsetGeometryInstanceAttribute.toValue(Cartesian3.IDENTITY);
|
|
*/
|
|
Primitive.prototype.getGeometryInstanceAttributes = function (id) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(id)) {
|
|
throw new DeveloperError("id is required");
|
|
}
|
|
if (!defined(this._batchTable)) {
|
|
throw new DeveloperError(
|
|
"must call update before calling getGeometryInstanceAttributes"
|
|
);
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var index = -1;
|
|
var lastIndex = this._lastPerInstanceAttributeIndex;
|
|
var ids = this._instanceIds;
|
|
var length = ids.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
var curIndex = (lastIndex + i) % length;
|
|
if (id === ids[curIndex]) {
|
|
index = curIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index === -1) {
|
|
return undefined;
|
|
}
|
|
|
|
var attributes = this._perInstanceAttributeCache[index];
|
|
if (defined(attributes)) {
|
|
return attributes;
|
|
}
|
|
|
|
var batchTable = this._batchTable;
|
|
var perInstanceAttributeIndices = this._batchTableAttributeIndices;
|
|
attributes = {};
|
|
var properties = {};
|
|
|
|
for (var name in perInstanceAttributeIndices) {
|
|
if (perInstanceAttributeIndices.hasOwnProperty(name)) {
|
|
var attributeIndex = perInstanceAttributeIndices[name];
|
|
properties[name] = {
|
|
get: createGetFunction(batchTable, index, attributeIndex),
|
|
set: createSetFunction(batchTable, index, attributeIndex, this, name),
|
|
};
|
|
}
|
|
}
|
|
|
|
createBoundingSphereProperties(this, properties, index);
|
|
createPickIdProperty(this, properties, index);
|
|
Object.defineProperties(attributes, properties);
|
|
|
|
this._lastPerInstanceAttributeIndex = index;
|
|
this._perInstanceAttributeCache[index] = attributes;
|
|
return attributes;
|
|
};
|
|
|
|
/**
|
|
* Returns true if this object was destroyed; otherwise, false.
|
|
* <p>
|
|
* If this object was destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
|
|
* </p>
|
|
*
|
|
* @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
|
*
|
|
* @see Primitive#destroy
|
|
*/
|
|
Primitive.prototype.isDestroyed = function () {
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
|
|
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
|
|
* <p>
|
|
* Once an object is destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
|
|
* assign the return value (<code>undefined</code>) to the object as done in the example.
|
|
* </p>
|
|
*
|
|
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
|
*
|
|
*
|
|
* @example
|
|
* e = e && e.destroy();
|
|
*
|
|
* @see Primitive#isDestroyed
|
|
*/
|
|
Primitive.prototype.destroy = function () {
|
|
var length;
|
|
var i;
|
|
|
|
this._sp = this._sp && this._sp.destroy();
|
|
this._spDepthFail = this._spDepthFail && this._spDepthFail.destroy();
|
|
|
|
var va = this._va;
|
|
length = va.length;
|
|
for (i = 0; i < length; ++i) {
|
|
va[i].destroy();
|
|
}
|
|
this._va = undefined;
|
|
|
|
var pickIds = this._pickIds;
|
|
length = pickIds.length;
|
|
for (i = 0; i < length; ++i) {
|
|
pickIds[i].destroy();
|
|
}
|
|
this._pickIds = undefined;
|
|
|
|
this._batchTable = this._batchTable && this._batchTable.destroy();
|
|
|
|
//These objects may be fairly large and reference other large objects (like Entities)
|
|
//We explicitly set them to undefined here so that the memory can be freed
|
|
//even if a reference to the destroyed Primitive has been kept around.
|
|
this._instanceIds = undefined;
|
|
this._perInstanceAttributeCache = undefined;
|
|
this._attributeLocations = undefined;
|
|
|
|
return destroyObject(this);
|
|
};
|
|
|
|
function setReady(primitive, frameState, state, error) {
|
|
primitive._error = error;
|
|
primitive._state = state;
|
|
frameState.afterRender.push(function () {
|
|
primitive._ready =
|
|
primitive._state === PrimitiveState.COMPLETE ||
|
|
primitive._state === PrimitiveState.FAILED;
|
|
if (!defined(error)) {
|
|
primitive._readyPromise.resolve(primitive);
|
|
} else {
|
|
primitive._readyPromise.reject(error);
|
|
}
|
|
});
|
|
}
|
|
export default Primitive;
|