import BoundingRectangle from "../Core/BoundingRectangle.js"; import Cartesian2 from "../Core/Cartesian2.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import Color from "../Core/Color.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js"; import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; /** * A graphical point positioned in the 3D scene, that is created * and rendered using a {@link PointPrimitiveCollection}. A point is created and its initial * properties are set by calling {@link PointPrimitiveCollection#add}. * * @alias PointPrimitive * * @performance Reading a property, e.g., {@link PointPrimitive#show}, is constant time. * Assigning to a property is constant time but results in * CPU to GPU traffic when {@link PointPrimitiveCollection#update} is called. The per-pointPrimitive traffic is * the same regardless of how many properties were updated. If most pointPrimitives in a collection need to be * updated, it may be more efficient to clear the collection with {@link PointPrimitiveCollection#removeAll} * and add new pointPrimitives instead of modifying each one. * * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near * * @see PointPrimitiveCollection * @see PointPrimitiveCollection#add * * @internalConstructor * @class * * @demo {@link https://sandcastle.cesium.com/index.html?src=Points.html|Cesium Sandcastle Points Demo} */ function PointPrimitive(options, pointPrimitiveCollection) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); if ( defined(options.disableDepthTestDistance) && options.disableDepthTestDistance < 0.0 ) { throw new DeveloperError( "disableDepthTestDistance must be greater than or equal to 0.0." ); } //>>includeEnd('debug'); let translucencyByDistance = options.translucencyByDistance; let scaleByDistance = options.scaleByDistance; let distanceDisplayCondition = options.distanceDisplayCondition; if (defined(translucencyByDistance)) { //>>includeStart('debug', pragmas.debug); if (translucencyByDistance.far <= translucencyByDistance.near) { throw new DeveloperError( "translucencyByDistance.far must be greater than translucencyByDistance.near." ); } //>>includeEnd('debug'); translucencyByDistance = NearFarScalar.clone(translucencyByDistance); } if (defined(scaleByDistance)) { //>>includeStart('debug', pragmas.debug); if (scaleByDistance.far <= scaleByDistance.near) { throw new DeveloperError( "scaleByDistance.far must be greater than scaleByDistance.near." ); } //>>includeEnd('debug'); scaleByDistance = NearFarScalar.clone(scaleByDistance); } if (defined(distanceDisplayCondition)) { //>>includeStart('debug', pragmas.debug); if (distanceDisplayCondition.far <= distanceDisplayCondition.near) { throw new DeveloperError( "distanceDisplayCondition.far must be greater than distanceDisplayCondition.near." ); } //>>includeEnd('debug'); distanceDisplayCondition = DistanceDisplayCondition.clone( distanceDisplayCondition ); } this._show = defaultValue(options.show, true); this._position = Cartesian3.clone( defaultValue(options.position, Cartesian3.ZERO) ); this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D this._color = Color.clone(defaultValue(options.color, Color.WHITE)); this._outlineColor = Color.clone( defaultValue(options.outlineColor, Color.TRANSPARENT) ); this._outlineWidth = defaultValue(options.outlineWidth, 0.0); this._pixelSize = defaultValue(options.pixelSize, 10.0); this._scaleByDistance = scaleByDistance; this._translucencyByDistance = translucencyByDistance; this._distanceDisplayCondition = distanceDisplayCondition; this._disableDepthTestDistance = defaultValue( options.disableDepthTestDistance, 0.0 ); this._id = options.id; this._collection = defaultValue(options.collection, pointPrimitiveCollection); this._clusterShow = true; this._pickId = undefined; this._pointPrimitiveCollection = pointPrimitiveCollection; this._dirty = false; this._index = -1; //Used only by PointPrimitiveCollection } const SHOW_INDEX = (PointPrimitive.SHOW_INDEX = 0); const POSITION_INDEX = (PointPrimitive.POSITION_INDEX = 1); const COLOR_INDEX = (PointPrimitive.COLOR_INDEX = 2); const OUTLINE_COLOR_INDEX = (PointPrimitive.OUTLINE_COLOR_INDEX = 3); const OUTLINE_WIDTH_INDEX = (PointPrimitive.OUTLINE_WIDTH_INDEX = 4); const PIXEL_SIZE_INDEX = (PointPrimitive.PIXEL_SIZE_INDEX = 5); const SCALE_BY_DISTANCE_INDEX = (PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6); const TRANSLUCENCY_BY_DISTANCE_INDEX = (PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7); const DISTANCE_DISPLAY_CONDITION_INDEX = (PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX = 8); const DISABLE_DEPTH_DISTANCE_INDEX = (PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX = 9); PointPrimitive.NUMBER_OF_PROPERTIES = 10; function makeDirty(pointPrimitive, propertyChanged) { const pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; if (defined(pointPrimitiveCollection)) { pointPrimitiveCollection._updatePointPrimitive( pointPrimitive, propertyChanged ); pointPrimitive._dirty = true; } } Object.defineProperties(PointPrimitive.prototype, { /** * Determines if this point will be shown. Use this to hide or show a point, instead * of removing it and re-adding it to the collection. * @memberof PointPrimitive.prototype * @type {Boolean} */ show: { get: function () { return this._show; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); if (this._show !== value) { this._show = value; makeDirty(this, SHOW_INDEX); } }, }, /** * Gets or sets the Cartesian position of this point. * @memberof PointPrimitive.prototype * @type {Cartesian3} */ position: { get: function () { return this._position; }, set: function (value) { //>>includeStart('debug', pragmas.debug) if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); const position = this._position; if (!Cartesian3.equals(position, value)) { Cartesian3.clone(value, position); Cartesian3.clone(value, this._actualPosition); makeDirty(this, POSITION_INDEX); } }, }, /** * Gets or sets near and far scaling properties of a point based on the point's distance from the camera. * A point's scale will interpolate between the {@link NearFarScalar#nearValue} and * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the point's scale remains clamped to the nearest bound. This scale * multiplies the pixelSize and outlineWidth to affect the total size of the point. If undefined, * scaleByDistance will be disabled. * @memberof PointPrimitive.prototype * @type {NearFarScalar} * * @example * // Example 1. * // Set a pointPrimitive's scaleByDistance to scale to 15 when the * // camera is 1500 meters from the pointPrimitive and disappear as * // the camera distance approaches 8.0e6 meters. * p.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 15, 8.0e6, 0.0); * * @example * // Example 2. * // disable scaling by distance * p.scaleByDistance = undefined; */ scaleByDistance: { get: function () { return this._scaleByDistance; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (defined(value) && value.far <= value.near) { throw new DeveloperError( "far distance must be greater than near distance." ); } //>>includeEnd('debug'); const scaleByDistance = this._scaleByDistance; if (!NearFarScalar.equals(scaleByDistance, value)) { this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance); makeDirty(this, SCALE_BY_DISTANCE_INDEX); } }, }, /** * Gets or sets near and far translucency properties of a point based on the point's distance from the camera. * A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and * {@link NearFarScalar#farValue} while the camera distance falls within the lower and upper bounds * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}. * Outside of these ranges the point's translucency remains clamped to the nearest bound. If undefined, * translucencyByDistance will be disabled. * @memberof PointPrimitive.prototype * @type {NearFarScalar} * * @example * // Example 1. * // Set a point's translucency to 1.0 when the * // camera is 1500 meters from the point and disappear as * // the camera distance approaches 8.0e6 meters. * p.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0); * * @example * // Example 2. * // disable translucency by distance * p.translucencyByDistance = undefined; */ translucencyByDistance: { get: function () { return this._translucencyByDistance; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (defined(value) && value.far <= value.near) { throw new DeveloperError( "far distance must be greater than near distance." ); } //>>includeEnd('debug'); const translucencyByDistance = this._translucencyByDistance; if (!NearFarScalar.equals(translucencyByDistance, value)) { this._translucencyByDistance = NearFarScalar.clone( value, translucencyByDistance ); makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX); } }, }, /** * Gets or sets the inner size of the point in pixels. * @memberof PointPrimitive.prototype * @type {Number} */ pixelSize: { get: function () { return this._pixelSize; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); if (this._pixelSize !== value) { this._pixelSize = value; makeDirty(this, PIXEL_SIZE_INDEX); } }, }, /** * Gets or sets the inner color of the point. * The red, green, blue, and alpha values are indicated by value's red, green, * blue, and alpha properties as shown in Example 1. These components range from 0.0 * (no intensity) to 1.0 (full intensity). * @memberof PointPrimitive.prototype * @type {Color} * * @example * // Example 1. Assign yellow. * p.color = Cesium.Color.YELLOW; * * @example * // Example 2. Make a pointPrimitive 50% translucent. * p.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5); */ color: { get: function () { return this._color; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); const color = this._color; if (!Color.equals(color, value)) { Color.clone(value, color); makeDirty(this, COLOR_INDEX); } }, }, /** * Gets or sets the outline color of the point. * @memberof PointPrimitive.prototype * @type {Color} */ outlineColor: { get: function () { return this._outlineColor; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); const outlineColor = this._outlineColor; if (!Color.equals(outlineColor, value)) { Color.clone(value, outlineColor); makeDirty(this, OUTLINE_COLOR_INDEX); } }, }, /** * Gets or sets the outline width in pixels. This width adds to pixelSize, * increasing the total size of the point. * @memberof PointPrimitive.prototype * @type {Number} */ outlineWidth: { get: function () { return this._outlineWidth; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (!defined(value)) { throw new DeveloperError("value is required."); } //>>includeEnd('debug'); if (this._outlineWidth !== value) { this._outlineWidth = value; makeDirty(this, OUTLINE_WIDTH_INDEX); } }, }, /** * Gets or sets the condition specifying at what distance from the camera that this point will be displayed. * @memberof PointPrimitive.prototype * @type {DistanceDisplayCondition} * @default undefined */ distanceDisplayCondition: { get: function () { return this._distanceDisplayCondition; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (defined(value) && value.far <= value.near) { throw new DeveloperError("far must be greater than near"); } //>>includeEnd('debug'); if ( !DistanceDisplayCondition.equals(this._distanceDisplayCondition, value) ) { this._distanceDisplayCondition = DistanceDisplayCondition.clone( value, this._distanceDisplayCondition ); makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX); } }, }, /** * Gets or sets the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. * When set to zero, the depth test is always applied. When set to Number.POSITIVE_INFINITY, the depth test is never applied. * @memberof PointPrimitive.prototype * @type {Number} * @default 0.0 */ disableDepthTestDistance: { get: function () { return this._disableDepthTestDistance; }, set: function (value) { if (this._disableDepthTestDistance !== value) { //>>includeStart('debug', pragmas.debug); if (!defined(value) || value < 0.0) { throw new DeveloperError( "disableDepthTestDistance must be greater than or equal to 0.0." ); } //>>includeEnd('debug'); this._disableDepthTestDistance = value; makeDirty(this, DISABLE_DEPTH_DISTANCE_INDEX); } }, }, /** * Gets or sets the user-defined value returned when the point is picked. * @memberof PointPrimitive.prototype * @type {*} */ id: { get: function () { return this._id; }, set: function (value) { this._id = value; if (defined(this._pickId)) { this._pickId.object.id = value; } }, }, /** * @private */ pickId: { get: function () { return this._pickId; }, }, /** * Determines whether or not this point will be shown or hidden because it was clustered. * @memberof PointPrimitive.prototype * @type {Boolean} * @private */ clusterShow: { get: function () { return this._clusterShow; }, set: function (value) { if (this._clusterShow !== value) { this._clusterShow = value; makeDirty(this, SHOW_INDEX); } }, }, }); PointPrimitive.prototype.getPickId = function (context) { if (!defined(this._pickId)) { this._pickId = context.createPickId({ primitive: this, collection: this._collection, id: this._id, }); } return this._pickId; }; PointPrimitive.prototype._getActualPosition = function () { return this._actualPosition; }; PointPrimitive.prototype._setActualPosition = function (value) { Cartesian3.clone(value, this._actualPosition); makeDirty(this, POSITION_INDEX); }; const tempCartesian3 = new Cartesian4(); PointPrimitive._computeActualPosition = function ( position, frameState, modelMatrix ) { if (frameState.mode === SceneMode.SCENE3D) { return position; } Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3); return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3); }; const scratchCartesian4 = new Cartesian4(); // This function is basically a stripped-down JavaScript version of PointPrimitiveCollectionVS.glsl PointPrimitive._computeScreenSpacePosition = function ( modelMatrix, position, scene, result ) { // Model to world coordinates const positionWorld = Matrix4.multiplyByVector( modelMatrix, Cartesian4.fromElements( position.x, position.y, position.z, 1, scratchCartesian4 ), scratchCartesian4 ); const positionWC = SceneTransforms.wgs84ToWindowCoordinates( scene, positionWorld, result ); return positionWC; }; /** * Computes the screen-space position of the point's origin. * The screen space origin is the top, left corner of the canvas; x increases from * left to right, and y increases from top to bottom. * * @param {Scene} scene The scene. * @param {Cartesian2} [result] The object onto which to store the result. * @returns {Cartesian2} The screen-space position of the point. * * @exception {DeveloperError} PointPrimitive must be in a collection. * * @example * console.log(p.computeScreenSpacePosition(scene).toString()); */ PointPrimitive.prototype.computeScreenSpacePosition = function (scene, result) { const pointPrimitiveCollection = this._pointPrimitiveCollection; if (!defined(result)) { result = new Cartesian2(); } //>>includeStart('debug', pragmas.debug); if (!defined(pointPrimitiveCollection)) { throw new DeveloperError("PointPrimitive must be in a collection."); } if (!defined(scene)) { throw new DeveloperError("scene is required."); } //>>includeEnd('debug'); const modelMatrix = pointPrimitiveCollection.modelMatrix; const windowCoordinates = PointPrimitive._computeScreenSpacePosition( modelMatrix, this._actualPosition, scene, result ); if (!defined(windowCoordinates)) { return undefined; } windowCoordinates.y = scene.canvas.clientHeight - windowCoordinates.y; return windowCoordinates; }; /** * Gets a point's screen space bounding box centered around screenSpacePosition. * @param {PointPrimitive} point The point to get the screen space bounding box for. * @param {Cartesian2} screenSpacePosition The screen space center of the label. * @param {BoundingRectangle} [result] The object onto which to store the result. * @returns {BoundingRectangle} The screen space bounding box. * * @private */ PointPrimitive.getScreenSpaceBoundingBox = function ( point, screenSpacePosition, result ) { const size = point.pixelSize; const halfSize = size * 0.5; const x = screenSpacePosition.x - halfSize; const y = screenSpacePosition.y - halfSize; const width = size; const height = size; if (!defined(result)) { result = new BoundingRectangle(); } result.x = x; result.y = y; result.width = width; result.height = height; return result; }; /** * Determines if this point equals another point. Points are equal if all their properties * are equal. Points in different collections can be equal. * * @param {PointPrimitive} other The point to compare for equality. * @returns {Boolean} true if the points are equal; otherwise, false. */ PointPrimitive.prototype.equals = function (other) { return ( this === other || (defined(other) && this._id === other._id && Cartesian3.equals(this._position, other._position) && Color.equals(this._color, other._color) && this._pixelSize === other._pixelSize && this._outlineWidth === other._outlineWidth && this._show === other._show && Color.equals(this._outlineColor, other._outlineColor) && NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) && NearFarScalar.equals( this._translucencyByDistance, other._translucencyByDistance ) && DistanceDisplayCondition.equals( this._distanceDisplayCondition, other._distanceDisplayCondition ) && this._disableDepthTestDistance === other._disableDepthTestDistance) ); }; PointPrimitive.prototype._destroy = function () { this._pickId = this._pickId && this._pickId.destroy(); this._pointPrimitiveCollection = undefined; }; export default PointPrimitive;