/*global define*/
define([
'../Core/BoundingSphere',
'../Core/BoxGeometry',
'../Core/Cartesian3',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Matrix4',
'../Core/VertexFormat',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArray',
'../Shaders/EllipsoidFS',
'../Shaders/EllipsoidVS',
'./BlendingState',
'./CullFace',
'./Material',
'./Pass',
'./SceneMode'
], function(
BoundingSphere,
BoxGeometry,
Cartesian3,
combine,
defaultValue,
defined,
destroyObject,
DeveloperError,
Matrix4,
VertexFormat,
BufferUsage,
DrawCommand,
RenderState,
ShaderProgram,
ShaderSource,
VertexArray,
EllipsoidFS,
EllipsoidVS,
BlendingState,
CullFace,
Material,
Pass,
SceneMode) {
'use strict';
var attributeLocations = {
position : 0
};
/**
* A renderable ellipsoid. It can also draw spheres when the three {@link EllipsoidPrimitive#radii} components are equal.
* <p>
* This is only supported in 3D. The ellipsoid is not shown in 2D or Columbus view.
* </p>
*
* @alias EllipsoidPrimitive
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Cartesian3} [options.center=Cartesian3.ZERO] The center of the ellipsoid in the ellipsoid's model coordinates.
* @param {Cartesian3} [options.radii] The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Material} [options.material=Material.ColorType] The surface appearance of the primitive.
* @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
*
* @private
*/
function EllipsoidPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The center of the ellipsoid in the ellipsoid's model coordinates.
* <p>
* The default is {@link Cartesian3.ZERO}.
* </p>
*
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*
* @see EllipsoidPrimitive#modelMatrix
*/
this.center = Cartesian3.clone(defaultValue(options.center, Cartesian3.ZERO));
this._center = new Cartesian3();
/**
* The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
* When these are the same, the ellipsoid is a sphere.
* <p>
* The default is <code>undefined</code>. The ellipsoid is not drawn until a radii is provided.
* </p>
*
* @type {Cartesian3}
* @default undefined
*
*
* @example
* // A sphere with a radius of 2.0
* e.radii = new Cesium.Cartesian3(2.0, 2.0, 2.0);
*
* @see EllipsoidPrimitive#modelMatrix
*/
this.radii = Cartesian3.clone(options.radii);
this._radii = new Cartesian3();
this._oneOverEllipsoidRadiiSquared = new Cartesian3();
this._boundingSphere = new BoundingSphere();
/**
* The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
* When this is the identity matrix, the ellipsoid 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}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* e.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = new Matrix4();
this._computedModelMatrix = new Matrix4();
/**
* Determines if the ellipsoid primitive will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The surface appearance of the ellipsoid. This can be one of several built-in {@link Material} objects or a custom material, scripted with
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
* <p>
* The default material is <code>Material.ColorType</code>.
* </p>
*
* @type {Material}
* @default Material.fromType(Material.ColorType)
*
*
* @example
* // 1. Change the color of the default material to yellow
* e.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
*
* // 2. Change material to horizontal stripes
* e.material = Cesium.Material.fromType(Cesium.Material.StripeType);
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = defaultValue(options.material, Material.fromType(Material.ColorType));
this._material = undefined;
this._translucent = undefined;
/**
* User-defined object returned when the ellipsoid is picked.
*
* @type Object
*
* @default undefined
*
* @see Scene#pick
*/
this.id = options.id;
this._id = undefined;
/**
* 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.onlySunLighting = defaultValue(options.onlySunLighting, false);
this._onlySunLighting = false;
/**
* @private
*/
this._depthTestEnabled = defaultValue(options.depthTestEnabled, true);
this._sp = undefined;
this._rs = undefined;
this._va = undefined;
this._pickSP = undefined;
this._pickId = undefined;
this._colorCommand = new DrawCommand({
owner : defaultValue(options._owner, this)
});
this._pickCommand = new DrawCommand({
owner : defaultValue(options._owner, this)
});
var that = this;
this._uniforms = {
u_radii : function() {
return that.radii;
},
u_oneOverEllipsoidRadiiSquared : function() {
return that._oneOverEllipsoidRadiiSquared;
}
};
this._pickUniforms = {
czm_pickColor : function() {
return that._pickId.color;
}
};
}
function getVertexArray(context) {
var vertexArray = context.cache.ellipsoidPrimitive_vertexArray;
if (defined(vertexArray)) {
return vertexArray;
}
var geometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
dimensions : new Cartesian3(2.0, 2.0, 2.0),
vertexFormat : VertexFormat.POSITION_ONLY
}));
vertexArray = VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : attributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW,
interleave : true
});
context.cache.ellipsoidPrimitive_vertexArray = vertexArray;
return vertexArray;
}
/**
* 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} this.material must be defined.
*/
EllipsoidPrimitive.prototype.update = function(frameState) {
if (!this.show ||
(frameState.mode !== SceneMode.SCENE3D) ||
(!defined(this.center)) ||
(!defined(this.radii))) {
return;
}
//>>includeStart('debug', pragmas.debug);
if (!defined(this.material)) {
throw new DeveloperError('this.material must be defined.');
}
//>>includeEnd('debug');
var context = frameState.context;
var translucent = this.material.isTranslucent();
var translucencyChanged = this._translucent !== translucent;
if (!defined(this._rs) || translucencyChanged) {
this._translucent = translucent;
// If this render state is ever updated to use a non-default
// depth range, the hard-coded values in EllipsoidVS.glsl need
// to be updated as well.
this._rs = RenderState.fromCache({
// Cull front faces - not back faces - so the ellipsoid doesn't
// disappear if the viewer enters the bounding box.
cull : {
enabled : true,
face : CullFace.FRONT
},
depthTest : {
enabled : this._depthTestEnabled
},
// Only write depth when EXT_frag_depth is supported since the depth for
// the bounding box is wrong; it is not the true depth of the ray casted ellipsoid.
depthMask : !translucent && context.fragmentDepth,
blending : translucent ? BlendingState.ALPHA_BLEND : undefined
});
}
if (!defined(this._va)) {
this._va = getVertexArray(context);
}
var boundingSphereDirty = false;
var radii = this.radii;
if (!Cartesian3.equals(this._radii, radii)) {
Cartesian3.clone(radii, this._radii);
var r = this._oneOverEllipsoidRadiiSquared;
r.x = 1.0 / (radii.x * radii.x);
r.y = 1.0 / (radii.y * radii.y);
r.z = 1.0 / (radii.z * radii.z);
boundingSphereDirty = true;
}
if (!Matrix4.equals(this.modelMatrix, this._modelMatrix) || !Cartesian3.equals(this.center, this._center)) {
Matrix4.clone(this.modelMatrix, this._modelMatrix);
Cartesian3.clone(this.center, this._center);
// Translate model coordinates used for rendering such that the origin is the center of the ellipsoid.
Matrix4.multiplyByTranslation(this.modelMatrix, this.center, this._computedModelMatrix);
boundingSphereDirty = true;
}
if (boundingSphereDirty) {
Cartesian3.clone(Cartesian3.ZERO, this._boundingSphere.center);
this._boundingSphere.radius = Cartesian3.maximumComponent(radii);
BoundingSphere.transform(this._boundingSphere, this._computedModelMatrix, this._boundingSphere);
}
var materialChanged = this._material !== this.material;
this._material = this.material;
this._material.update(context);
var lightingChanged = this.onlySunLighting !== this._onlySunLighting;
this._onlySunLighting = this.onlySunLighting;
var colorCommand = this._colorCommand;
var fs;
// Recompile shader when material, lighting, or translucency changes
if (materialChanged || lightingChanged || translucencyChanged) {
fs = new ShaderSource({
sources : [this.material.shaderSource, EllipsoidFS]
});
if (this.onlySunLighting) {
fs.defines.push('ONLY_SUN_LIGHTING');
}
if (!translucent && context.fragmentDepth) {
fs.defines.push('WRITE_DEPTH');
}
this._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._sp,
vertexShaderSource : EllipsoidVS,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
colorCommand.vertexArray = this._va;
colorCommand.renderState = this._rs;
colorCommand.shaderProgram = this._sp;
colorCommand.uniformMap = combine(this._uniforms, this.material._uniforms);
colorCommand.executeInClosestFrustum = translucent;
}
var commandList = frameState.commandList;
var passes = frameState.passes;
if (passes.render) {
colorCommand.boundingVolume = this._boundingSphere;
colorCommand.debugShowBoundingVolume = this.debugShowBoundingVolume;
colorCommand.modelMatrix = this._computedModelMatrix;
colorCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
commandList.push(colorCommand);
}
if (passes.pick) {
var pickCommand = this._pickCommand;
if (!defined(this._pickId) || (this._id !== this.id)) {
this._id = this.id;
this._pickId = this._pickId && this._pickId.destroy();
this._pickId = context.createPickId({
primitive : this,
id : this.id
});
}
// Recompile shader when material changes
if (materialChanged || lightingChanged || !defined(this._pickSP)) {
fs = new ShaderSource({
sources : [this.material.shaderSource, EllipsoidFS],
pickColorQualifier : 'uniform'
});
if (this.onlySunLighting) {
fs.defines.push('ONLY_SUN_LIGHTING');
}
if (!translucent && context.fragmentDepth) {
fs.defines.push('WRITE_DEPTH');
}
this._pickSP = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._pickSP,
vertexShaderSource : EllipsoidVS,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
pickCommand.vertexArray = this._va;
pickCommand.renderState = this._rs;
pickCommand.shaderProgram = this._pickSP;
pickCommand.uniformMap = combine(combine(this._uniforms, this._pickUniforms), this.material._uniforms);
pickCommand.executeInClosestFrustum = translucent;
}
pickCommand.boundingVolume = this._boundingSphere;
pickCommand.modelMatrix = this._computedModelMatrix;
pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
commandList.push(pickCommand);
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
* 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.
*
* @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
*
* @see EllipsoidPrimitive#destroy
*/
EllipsoidPrimitive.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.
* <br /><br />
* 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.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* e = e && e.destroy();
*
* @see EllipsoidPrimitive#isDestroyed
*/
EllipsoidPrimitive.prototype.destroy = function() {
this._sp = this._sp && this._sp.destroy();
this._pickSP = this._pickSP && this._pickSP.destroy();
this._pickId = this._pickId && this._pickId.destroy();
return destroyObject(this);
};
return EllipsoidPrimitive;
});