feat: 实现供水运营专题大屏BI可视化
- 新增OperationDashboard.vue全屏大屏组件 - 添加6个核心KPI指标卡片: 进水总量/出水总量/产销差率/营收额/平均水质/报警次数 - 实现6个ECharts图表: 供水趋势/水质分布/报警统计/管网空间/设备状态/营收分析 - 创建静态HTML版本operation-dashboard.html,使用CDN加载Vue3/ECharts/Element Plus - 更新路由配置,添加/operation路径支持 - 修复nextTick导入问题,优化build脚本 🚧 开发者: bot_dev1 📝 任务: #38 [BI] 运营仪表盘 + 供水专题大屏
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* The alpha rendering mode of the material.
|
||||
*
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
const AlphaMode = {
|
||||
/**
|
||||
* The alpha value is ignored and the rendered output is fully opaque.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
OPAQUE: "OPAQUE",
|
||||
|
||||
/**
|
||||
* The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
MASK: "MASK",
|
||||
|
||||
/**
|
||||
* The rendered output is composited onto the destination with alpha blending.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
BLEND: "BLEND",
|
||||
};
|
||||
|
||||
Object.freeze(AlphaMode);
|
||||
|
||||
export default AlphaMode;
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
import clone from "../Core/clone.js";
|
||||
import combine from "../Core/combine.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import BlendingState from "./BlendingState.js";
|
||||
import CullFace from "./CullFace.js";
|
||||
|
||||
/**
|
||||
* An appearance defines the full GLSL vertex and fragment shaders and the
|
||||
* render state used to draw a {@link Primitive}. All appearances implement
|
||||
* this base <code>Appearance</code> interface.
|
||||
*
|
||||
* @alias Appearance
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} [options] Object with the following properties:
|
||||
* @param {boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link Appearance#renderState} has alpha blending enabled.
|
||||
* @param {boolean} [options.closed=false] When <code>true</code>, the geometry is expected to be closed so {@link Appearance#renderState} has backface culling enabled.
|
||||
* @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
|
||||
* @param {string} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
|
||||
* @param {string} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
|
||||
* @param {object} [options.renderState] Optional render state to override the default render state.
|
||||
*
|
||||
* @see MaterialAppearance
|
||||
* @see EllipsoidSurfaceAppearance
|
||||
* @see PerInstanceColorAppearance
|
||||
* @see DebugAppearance
|
||||
* @see PolylineColorAppearance
|
||||
* @see PolylineMaterialAppearance
|
||||
*
|
||||
* @demo {@link https://sandcastle.cesium.com/index.html?id=geometry-and-appearances|Geometry and Appearances Demo}
|
||||
*/
|
||||
function Appearance(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
/**
|
||||
* The material used to determine the fragment color. Unlike other {@link Appearance}
|
||||
* properties, this is not read-only, so an appearance's material can change on the fly.
|
||||
*
|
||||
* @type Material
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric}
|
||||
*/
|
||||
this.material = options.material;
|
||||
|
||||
/**
|
||||
* When <code>true</code>, the geometry is expected to appear translucent.
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
this.translucent = options.translucent ?? true;
|
||||
|
||||
this._vertexShaderSource = options.vertexShaderSource;
|
||||
this._fragmentShaderSource = options.fragmentShaderSource;
|
||||
this._renderState = options.renderState;
|
||||
this._closed = options.closed ?? false;
|
||||
}
|
||||
|
||||
Object.defineProperties(Appearance.prototype, {
|
||||
/**
|
||||
* The GLSL source code for the vertex shader.
|
||||
*
|
||||
* @memberof Appearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
vertexShaderSource: {
|
||||
get: function () {
|
||||
return this._vertexShaderSource;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The GLSL source code for the fragment shader. The full fragment shader
|
||||
* source is built procedurally taking into account the {@link Appearance#material}.
|
||||
* Use {@link Appearance#getFragmentShaderSource} to get the full source.
|
||||
*
|
||||
* @memberof Appearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
fragmentShaderSource: {
|
||||
get: function () {
|
||||
return this._fragmentShaderSource;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The WebGL fixed-function state to use when rendering the geometry.
|
||||
*
|
||||
* @memberof Appearance.prototype
|
||||
*
|
||||
* @type {object}
|
||||
* @readonly
|
||||
*/
|
||||
renderState: {
|
||||
get: function () {
|
||||
return this._renderState;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* When <code>true</code>, the geometry is expected to be closed.
|
||||
*
|
||||
* @memberof Appearance.prototype
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
closed: {
|
||||
get: function () {
|
||||
return this._closed;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Procedurally creates the full GLSL fragment shader source for this appearance
|
||||
* taking into account {@link Appearance#fragmentShaderSource} and {@link Appearance#material}.
|
||||
*
|
||||
* @returns {string} The full GLSL fragment shader source.
|
||||
*/
|
||||
Appearance.prototype.getFragmentShaderSource = function () {
|
||||
const parts = [];
|
||||
if (this.flat) {
|
||||
parts.push("#define FLAT");
|
||||
}
|
||||
if (this.faceForward) {
|
||||
parts.push("#define FACE_FORWARD");
|
||||
}
|
||||
if (defined(this.material)) {
|
||||
parts.push(this.material.shaderSource);
|
||||
}
|
||||
parts.push(this.fragmentShaderSource);
|
||||
|
||||
return parts.join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the geometry is translucent based on {@link Appearance#translucent} and {@link Material#isTranslucent}.
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if the appearance is translucent.
|
||||
*/
|
||||
Appearance.prototype.isTranslucent = function () {
|
||||
return (
|
||||
(defined(this.material) && this.material.isTranslucent()) ||
|
||||
(!defined(this.material) && this.translucent)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a render state. This is not the final render state instance; instead,
|
||||
* it can contain a subset of render state properties identical to the render state
|
||||
* created in the context.
|
||||
*
|
||||
* @returns {object} The render state.
|
||||
*/
|
||||
Appearance.prototype.getRenderState = function () {
|
||||
const translucent = this.isTranslucent();
|
||||
const rs = clone(this.renderState, false);
|
||||
if (translucent) {
|
||||
rs.depthMask = false;
|
||||
rs.blending = BlendingState.ALPHA_BLEND;
|
||||
} else {
|
||||
rs.depthMask = true;
|
||||
}
|
||||
return rs;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Appearance.getDefaultRenderState = function (translucent, closed, existing) {
|
||||
let rs = {
|
||||
depthTest: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (translucent) {
|
||||
rs.depthMask = false;
|
||||
rs.blending = BlendingState.ALPHA_BLEND;
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
rs.cull = {
|
||||
enabled: true,
|
||||
face: CullFace.BACK,
|
||||
};
|
||||
}
|
||||
|
||||
if (defined(existing)) {
|
||||
rs = combine(existing, rs, true);
|
||||
}
|
||||
|
||||
return rs;
|
||||
};
|
||||
export default Appearance;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* ArcGisBaseMapType enumerates the ArcGIS image tile layers that are supported by default.
|
||||
*
|
||||
* @enum {number}
|
||||
* @see ArcGisMapServerImageryProvider
|
||||
*/
|
||||
const ArcGisBaseMapType = {
|
||||
SATELLITE: 1,
|
||||
OCEANS: 2,
|
||||
HILLSHADE: 3,
|
||||
};
|
||||
|
||||
Object.freeze(ArcGisBaseMapType);
|
||||
|
||||
export default ArcGisBaseMapType;
|
||||
Generated
Vendored
+875
@@ -0,0 +1,875 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Event from "../Core/Event.js";
|
||||
import GeographicProjection from "../Core/GeographicProjection.js";
|
||||
import GeographicTilingScheme from "../Core/GeographicTilingScheme.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
import WebMercatorProjection from "../Core/WebMercatorProjection.js";
|
||||
import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
|
||||
import ArcGisMapService from "./ArcGisMapService.js";
|
||||
import DiscardMissingTileImagePolicy from "./DiscardMissingTileImagePolicy.js";
|
||||
import ImageryLayerFeatureInfo from "./ImageryLayerFeatureInfo.js";
|
||||
import ImageryProvider from "./ImageryProvider.js";
|
||||
import ArcGisBaseMapType from "./ArcGisBaseMapType.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
|
||||
/**
|
||||
* @typedef {object} ArcGisMapServerImageryProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the ArcGisMapServerImageryProvider constructor
|
||||
*
|
||||
* @property {TileDiscardPolicy} [tileDiscardPolicy] The policy that determines if a tile
|
||||
* is invalid and should be discarded. If this value is not specified, a default
|
||||
* {@link DiscardMissingTileImagePolicy} is used for tiled map servers, and a
|
||||
* {@link NeverTileDiscardPolicy} is used for non-tiled map servers. In the former case,
|
||||
* we request tile 0,0 at the maximum tile level and check pixels (0,0), (200,20), (20,200),
|
||||
* (80,110), and (160, 130). If all of these pixels are transparent, the discard check is
|
||||
* disabled and no tiles are discarded. If any of them have a non-transparent color, any
|
||||
* tile that has the same values in these pixel locations is discarded. The end result of
|
||||
* these defaults should be correct tile discarding for a standard ArcGIS Server. To ensure
|
||||
* that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
|
||||
* parameter.
|
||||
* @property {boolean} [usePreCachedTilesIfAvailable=true] If true, the server's pre-cached
|
||||
* tiles are used if they are available. Exporting Tiles is only supported with deprecated APIs.
|
||||
* @property {string} [layers] A comma-separated list of the layers to show, or undefined if all layers should be shown.
|
||||
* @property {boolean} [enablePickFeatures=true] If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will invoke
|
||||
* the Identify service on the MapServer and return the features included in the response. If false,
|
||||
* {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
|
||||
* without communicating with the server. Set this property to false if you don't want this provider's features to
|
||||
* be pickable. Can be overridden by setting the {@link ArcGisMapServerImageryProvider#enablePickFeatures} property on the object.
|
||||
* @property {Rectangle} [rectangle=Rectangle.MAX_VALUE] The rectangle of the layer. This parameter is ignored when accessing
|
||||
* a tiled layer.
|
||||
* @property {TilingScheme} [tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
|
||||
* This parameter is ignored when accessing a tiled server.
|
||||
* @property {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid. If the tilingScheme is specified and used,
|
||||
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
|
||||
* parameter is specified, the default ellipsoid is used.
|
||||
* @property {Credit|string} [credit] A credit for the data source, which is displayed on the canvas. This parameter is ignored when accessing a tiled server.
|
||||
* @property {number} [tileWidth=256] The width of each tile in pixels. This parameter is ignored when accessing a tiled server.
|
||||
* @property {number} [tileHeight=256] The height of each tile in pixels. This parameter is ignored when accessing a tiled server.
|
||||
* @property {number} [maximumLevel] The maximum tile level to request, or undefined if there is no maximum. This parameter is ignored when accessing
|
||||
* a tiled server.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used to track creation details while fetching initial metadata
|
||||
*
|
||||
* @constructor
|
||||
* @private
|
||||
*
|
||||
* @param {ArcGisMapServerImageryProvider.ConstructorOptions} options An object describing initialization options
|
||||
*/
|
||||
function ImageryProviderBuilder(options) {
|
||||
this.useTiles = options.usePreCachedTilesIfAvailable ?? true;
|
||||
|
||||
const ellipsoid = options.ellipsoid;
|
||||
this.tilingScheme =
|
||||
options.tilingScheme ??
|
||||
new GeographicTilingScheme({ ellipsoid: ellipsoid });
|
||||
this.rectangle = options.rectangle ?? this.tilingScheme.rectangle;
|
||||
this.ellipsoid = ellipsoid;
|
||||
|
||||
let credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
this.credit = credit;
|
||||
this.tileCredits = undefined;
|
||||
this.tileDiscardPolicy = options.tileDiscardPolicy;
|
||||
|
||||
this.tileWidth = options.tileWidth ?? 256;
|
||||
this.tileHeight = options.tileHeight ?? 256;
|
||||
this.maximumLevel = options.maximumLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete ArcGisMapServerImageryProvider creation based on builder values.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {ArcGisMapServerImageryProvider} provider
|
||||
*/
|
||||
ImageryProviderBuilder.prototype.build = function (provider) {
|
||||
provider._useTiles = this.useTiles;
|
||||
provider._tilingScheme = this.tilingScheme;
|
||||
provider._rectangle = this.rectangle;
|
||||
provider._credit = this.credit;
|
||||
provider._tileCredits = this.tileCredits;
|
||||
provider._tileDiscardPolicy = this.tileDiscardPolicy;
|
||||
provider._tileWidth = this.tileWidth;
|
||||
provider._tileHeight = this.tileHeight;
|
||||
provider._maximumLevel = this.maximumLevel;
|
||||
|
||||
// Install the default tile discard policy if none has been supplied.
|
||||
if (this.useTiles && !defined(this.tileDiscardPolicy)) {
|
||||
provider._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
|
||||
missingImageUrl: buildImageResource(provider, 0, 0, this.maximumLevel)
|
||||
.url,
|
||||
pixelsToCheck: [
|
||||
new Cartesian2(0, 0),
|
||||
new Cartesian2(200, 20),
|
||||
new Cartesian2(20, 200),
|
||||
new Cartesian2(80, 110),
|
||||
new Cartesian2(160, 130),
|
||||
],
|
||||
disableCheckIfAllPixelsAreTransparent: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function metadataSuccess(data, imageryProviderBuilder) {
|
||||
const tileInfo = data.tileInfo;
|
||||
if (!defined(tileInfo)) {
|
||||
imageryProviderBuilder.useTiles = false;
|
||||
} else {
|
||||
imageryProviderBuilder.tileWidth = tileInfo.rows;
|
||||
imageryProviderBuilder.tileHeight = tileInfo.cols;
|
||||
|
||||
if (
|
||||
tileInfo.spatialReference.wkid === 102100 ||
|
||||
tileInfo.spatialReference.wkid === 102113
|
||||
) {
|
||||
imageryProviderBuilder.tilingScheme = new WebMercatorTilingScheme({
|
||||
ellipsoid: imageryProviderBuilder.ellipsoid,
|
||||
});
|
||||
} else if (data.tileInfo.spatialReference.wkid === 4326) {
|
||||
imageryProviderBuilder.tilingScheme = new GeographicTilingScheme({
|
||||
ellipsoid: imageryProviderBuilder.ellipsoid,
|
||||
});
|
||||
} else {
|
||||
const message = `Tile spatial reference WKID ${data.tileInfo.spatialReference.wkid} is not supported.`;
|
||||
throw new RuntimeError(message);
|
||||
}
|
||||
imageryProviderBuilder.maximumLevel = data.tileInfo.lods.length - 1;
|
||||
|
||||
if (defined(data.fullExtent)) {
|
||||
if (
|
||||
defined(data.fullExtent.spatialReference) &&
|
||||
defined(data.fullExtent.spatialReference.wkid)
|
||||
) {
|
||||
if (
|
||||
data.fullExtent.spatialReference.wkid === 102100 ||
|
||||
data.fullExtent.spatialReference.wkid === 102113
|
||||
) {
|
||||
const projection = new WebMercatorProjection();
|
||||
const extent = data.fullExtent;
|
||||
const sw = projection.unproject(
|
||||
new Cartesian3(
|
||||
Math.max(
|
||||
extent.xmin,
|
||||
-imageryProviderBuilder.tilingScheme.ellipsoid.maximumRadius *
|
||||
Math.PI,
|
||||
),
|
||||
Math.max(
|
||||
extent.ymin,
|
||||
-imageryProviderBuilder.tilingScheme.ellipsoid.maximumRadius *
|
||||
Math.PI,
|
||||
),
|
||||
0.0,
|
||||
),
|
||||
);
|
||||
const ne = projection.unproject(
|
||||
new Cartesian3(
|
||||
Math.min(
|
||||
extent.xmax,
|
||||
imageryProviderBuilder.tilingScheme.ellipsoid.maximumRadius *
|
||||
Math.PI,
|
||||
),
|
||||
Math.min(
|
||||
extent.ymax,
|
||||
imageryProviderBuilder.tilingScheme.ellipsoid.maximumRadius *
|
||||
Math.PI,
|
||||
),
|
||||
0.0,
|
||||
),
|
||||
);
|
||||
imageryProviderBuilder.rectangle = new Rectangle(
|
||||
sw.longitude,
|
||||
sw.latitude,
|
||||
ne.longitude,
|
||||
ne.latitude,
|
||||
);
|
||||
} else if (data.fullExtent.spatialReference.wkid === 4326) {
|
||||
imageryProviderBuilder.rectangle = Rectangle.fromDegrees(
|
||||
data.fullExtent.xmin,
|
||||
data.fullExtent.ymin,
|
||||
data.fullExtent.xmax,
|
||||
data.fullExtent.ymax,
|
||||
);
|
||||
} else {
|
||||
const extentMessage = `fullExtent.spatialReference WKID ${data.fullExtent.spatialReference.wkid} is not supported.`;
|
||||
throw new RuntimeError(extentMessage);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imageryProviderBuilder.rectangle =
|
||||
imageryProviderBuilder.tilingScheme.rectangle;
|
||||
}
|
||||
|
||||
imageryProviderBuilder.useTiles = true;
|
||||
}
|
||||
|
||||
if (defined(data.copyrightText) && data.copyrightText.length > 0) {
|
||||
if (defined(imageryProviderBuilder.credit)) {
|
||||
imageryProviderBuilder.tileCredits = [new Credit(data.copyrightText)];
|
||||
} else {
|
||||
imageryProviderBuilder.credit = new Credit(data.copyrightText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function metadataFailure(resource, error) {
|
||||
let message = `An error occurred while accessing ${resource.url}`;
|
||||
if (defined(error) && defined(error.message)) {
|
||||
message += `: ${error.message}`;
|
||||
}
|
||||
|
||||
throw new RuntimeError(message);
|
||||
}
|
||||
|
||||
async function requestMetadata(resource, imageryProviderBuilder) {
|
||||
const jsonResource = resource.getDerivedResource({
|
||||
queryParameters: {
|
||||
f: "json",
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await jsonResource.fetchJson();
|
||||
metadataSuccess(data, imageryProviderBuilder);
|
||||
} catch (error) {
|
||||
metadataFailure(resource, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <div class="notice">
|
||||
* This object is normally not instantiated directly, use {@link ArcGisMapServerImageryProvider.fromBasemapType} or {@link ArcGisMapServerImageryProvider.fromUrl}.
|
||||
* </div>
|
||||
*
|
||||
* Provides tiled imagery hosted by an ArcGIS MapServer. By default, the server's pre-cached tiles are
|
||||
* used, if available.
|
||||
*
|
||||
* <br/>
|
||||
*
|
||||
* An {@link https://developers.arcgis.com/documentation/mapping-apis-and-services/security| ArcGIS Access Token } is required to authenticate requests to an ArcGIS Image Tile service.
|
||||
* To access secure ArcGIS resources, it's required to create an ArcGIS developer
|
||||
* account or an ArcGIS online account, then implement an authentication method to obtain an access token.
|
||||
*
|
||||
* @alias ArcGisMapServerImageryProvider
|
||||
* @constructor
|
||||
*
|
||||
* @param {ArcGisMapServerImageryProvider.ConstructorOptions} [options] Object describing initialization options
|
||||
*
|
||||
* @see ArcGisMapServerImageryProvider.fromBasemapType
|
||||
* @see ArcGisMapServerImageryProvider.fromUrl
|
||||
*
|
||||
* @example
|
||||
* // Set the default access token for accessing ArcGIS Image Tile service
|
||||
* Cesium.ArcGisMapService.defaultAccessToken = "<ArcGIS Access Token>";
|
||||
*
|
||||
* // Add a base layer from a default ArcGIS basemap
|
||||
* const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
* baseLayer: Cesium.ImageryLayer.fromProviderAsync(
|
||||
* Cesium.ArcGisMapServerImageryProvider.fromBasemapType(
|
||||
* Cesium.ArcGisBaseMapType.SATELLITE
|
||||
* )
|
||||
* ),
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Create an imagery provider from the url directly
|
||||
* const esri = await Cesium.ArcGisMapServerImageryProvider.fromUrl(
|
||||
* "https://ibasemaps-api.arcgis.com/arcgis/rest/services/World_Imagery/MapServer", {
|
||||
* token: "<ArcGIS Access Token>"
|
||||
* });
|
||||
*
|
||||
* @see {@link https://developers.arcgis.com/rest/|ArcGIS Server REST API}
|
||||
* @see {@link https://developers.arcgis.com/documentation/mapping-apis-and-services/security| ArcGIS Access Token }
|
||||
|
||||
*/
|
||||
function ArcGisMapServerImageryProvider(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
this._defaultAlpha = undefined;
|
||||
this._defaultNightAlpha = undefined;
|
||||
this._defaultDayAlpha = undefined;
|
||||
this._defaultBrightness = undefined;
|
||||
this._defaultContrast = undefined;
|
||||
this._defaultHue = undefined;
|
||||
this._defaultSaturation = undefined;
|
||||
this._defaultGamma = undefined;
|
||||
this._defaultMinificationFilter = undefined;
|
||||
this._defaultMagnificationFilter = undefined;
|
||||
|
||||
this._tileDiscardPolicy = options.tileDiscardPolicy;
|
||||
this._tileWidth = options.tileWidth ?? 256;
|
||||
this._tileHeight = options.tileHeight ?? 256;
|
||||
this._maximumLevel = options.maximumLevel;
|
||||
this._tilingScheme =
|
||||
options.tilingScheme ??
|
||||
new GeographicTilingScheme({ ellipsoid: options.ellipsoid });
|
||||
this._useTiles = options.usePreCachedTilesIfAvailable ?? true;
|
||||
this._rectangle = options.rectangle ?? this._tilingScheme.rectangle;
|
||||
this._layers = options.layers;
|
||||
this._credit = options.credit;
|
||||
this._tileCredits = undefined;
|
||||
|
||||
let credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets a value indicating whether feature picking is enabled. If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will
|
||||
* invoke the "identify" operation on the ArcGIS server and return the features included in the response. If false,
|
||||
* {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
|
||||
* without communicating with the server.
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.enablePickFeatures = options.enablePickFeatures ?? true;
|
||||
|
||||
this._errorEvent = new Event();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ImageryProvider} which provides tiled imagery from an ArcGIS base map.
|
||||
* @param {ArcGisBaseMapType} style The style of the ArcGIS base map imagery. Valid options are {@link ArcGisBaseMapType.SATELLITE}, {@link ArcGisBaseMapType.OCEANS}, and {@link ArcGisBaseMapType.HILLSHADE}.
|
||||
* @param {ArcGisMapServerImageryProvider.ConstructorOptions} [options] Object describing initialization options.
|
||||
* @returns {Promise<ArcGisMapServerImageryProvider>} A promise that resolves to the created ArcGisMapServerImageryProvider.
|
||||
*
|
||||
* @example
|
||||
* // Set the default access token for accessing ArcGIS Image Tile service
|
||||
* Cesium.ArcGisMapService.defaultAccessToken = "<ArcGIS Access Token>";
|
||||
*
|
||||
* // Add a base layer from a default ArcGIS basemap
|
||||
* const provider = await Cesium.ArcGisMapServerImageryProvider.fromBasemapType(
|
||||
* Cesium.ArcGisBaseMapType.SATELLITE);
|
||||
*
|
||||
* @example
|
||||
* // Add a base layer from a default ArcGIS Basemap
|
||||
* const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
* baseLayer: Cesium.ImageryLayer.fromProviderAsync(
|
||||
* Cesium.ArcGisMapServerImageryProvider.fromBasemapType(
|
||||
* Cesium.ArcGisBaseMapType.HILLSHADE, {
|
||||
* token: "<ArcGIS Access Token>"
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* });
|
||||
*/
|
||||
|
||||
ArcGisMapServerImageryProvider.fromBasemapType = async function (
|
||||
style,
|
||||
options,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("style", style);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
let accessToken;
|
||||
let server;
|
||||
let warningCredit;
|
||||
switch (style) {
|
||||
case ArcGisBaseMapType.SATELLITE:
|
||||
{
|
||||
accessToken = options.token ?? ArcGisMapService.defaultAccessToken;
|
||||
server = Resource.createIfNeeded(
|
||||
ArcGisMapService.defaultWorldImageryServer,
|
||||
);
|
||||
server.appendForwardSlash();
|
||||
const defaultTokenCredit =
|
||||
ArcGisMapService.getDefaultTokenCredit(accessToken);
|
||||
if (defined(defaultTokenCredit)) {
|
||||
warningCredit = Credit.clone(defaultTokenCredit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ArcGisBaseMapType.OCEANS:
|
||||
{
|
||||
accessToken = options.token ?? ArcGisMapService.defaultAccessToken;
|
||||
server = Resource.createIfNeeded(
|
||||
ArcGisMapService.defaultWorldOceanServer,
|
||||
);
|
||||
server.appendForwardSlash();
|
||||
const defaultTokenCredit =
|
||||
ArcGisMapService.getDefaultTokenCredit(accessToken);
|
||||
if (defined(defaultTokenCredit)) {
|
||||
warningCredit = Credit.clone(defaultTokenCredit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ArcGisBaseMapType.HILLSHADE:
|
||||
{
|
||||
accessToken = options.token ?? ArcGisMapService.defaultAccessToken;
|
||||
server = Resource.createIfNeeded(
|
||||
ArcGisMapService.defaultWorldHillshadeServer,
|
||||
);
|
||||
server.appendForwardSlash();
|
||||
const defaultTokenCredit =
|
||||
ArcGisMapService.getDefaultTokenCredit(accessToken);
|
||||
if (defined(defaultTokenCredit)) {
|
||||
warningCredit = Credit.clone(defaultTokenCredit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
throw new DeveloperError(`Unsupported basemap type: ${style}`);
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
|
||||
return ArcGisMapServerImageryProvider.fromUrl(server, {
|
||||
...options,
|
||||
token: accessToken,
|
||||
credit: warningCredit,
|
||||
usePreCachedTilesIfAvailable: true, // ArcGIS Base Map Service Layers only support Tiled views
|
||||
});
|
||||
};
|
||||
|
||||
function buildImageResource(imageryProvider, x, y, level, request) {
|
||||
let resource;
|
||||
if (imageryProvider._useTiles) {
|
||||
resource = imageryProvider._resource.getDerivedResource({
|
||||
url: `tile/${level}/${y}/${x}`,
|
||||
request: request,
|
||||
});
|
||||
} else {
|
||||
const nativeRectangle =
|
||||
imageryProvider._tilingScheme.tileXYToNativeRectangle(x, y, level);
|
||||
const bbox = `${nativeRectangle.west},${nativeRectangle.south},${nativeRectangle.east},${nativeRectangle.north}`;
|
||||
|
||||
const query = {
|
||||
bbox: bbox,
|
||||
size: `${imageryProvider._tileWidth},${imageryProvider._tileHeight}`,
|
||||
format: "png32",
|
||||
transparent: true,
|
||||
f: "image",
|
||||
};
|
||||
|
||||
if (
|
||||
imageryProvider._tilingScheme.projection instanceof GeographicProjection
|
||||
) {
|
||||
query.bboxSR = 4326;
|
||||
query.imageSR = 4326;
|
||||
} else {
|
||||
query.bboxSR = 3857;
|
||||
query.imageSR = 3857;
|
||||
}
|
||||
if (imageryProvider.layers) {
|
||||
query.layers = `show:${imageryProvider.layers}`;
|
||||
}
|
||||
|
||||
resource = imageryProvider._resource.getDerivedResource({
|
||||
url: "export",
|
||||
request: request,
|
||||
queryParameters: query,
|
||||
});
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
Object.defineProperties(ArcGisMapServerImageryProvider.prototype, {
|
||||
/**
|
||||
* Gets the URL of the ArcGIS MapServer.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
url: {
|
||||
get: function () {
|
||||
return this._resource._url;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the ArcGIS token used to authenticate with the ArcGis MapServer service.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
token: {
|
||||
get: function () {
|
||||
return this._resource.queryParameters.token;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the proxy used by this provider.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {Proxy}
|
||||
* @readonly
|
||||
*/
|
||||
proxy: {
|
||||
get: function () {
|
||||
return this._resource.proxy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width of each tile, in pixels.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileWidth: {
|
||||
get: function () {
|
||||
return this._tileWidth;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the height of each tile, in pixels.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileHeight: {
|
||||
get: function () {
|
||||
return this._tileHeight;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum level-of-detail that can be requested.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumLevel: {
|
||||
get: function () {
|
||||
return this._maximumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum level-of-detail that can be requested.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
minimumLevel: {
|
||||
get: function () {
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tiling scheme used by this provider.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {TilingScheme}
|
||||
* @readonly
|
||||
*/
|
||||
tilingScheme: {
|
||||
get: function () {
|
||||
return this._tilingScheme;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the rectangle, in radians, of the imagery provided by this instance.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {Rectangle}
|
||||
* @readonly
|
||||
*/
|
||||
rectangle: {
|
||||
get: function () {
|
||||
return this._rectangle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tile discard policy. If not undefined, the discard policy is responsible
|
||||
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
|
||||
* returns undefined, no tiles are filtered.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {TileDiscardPolicy}
|
||||
* @readonly
|
||||
*/
|
||||
tileDiscardPolicy: {
|
||||
get: function () {
|
||||
return this._tileDiscardPolicy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
|
||||
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
|
||||
* are passed an instance of {@link TileProviderError}.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
errorEvent: {
|
||||
get: function () {
|
||||
return this._errorEvent;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
|
||||
* the source of the imagery.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return this._credit;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether this imagery provider is using pre-cached tiles from the
|
||||
* ArcGIS MapServer.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default true
|
||||
*/
|
||||
usingPrecachedTiles: {
|
||||
get: function () {
|
||||
return this._useTiles;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether or not the images provided by this imagery provider
|
||||
* include an alpha channel. If this property is false, an alpha channel, if present, will
|
||||
* be ignored. If this property is true, any images without an alpha channel will be treated
|
||||
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
|
||||
* and texture upload time are reduced.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default true
|
||||
*/
|
||||
hasAlphaChannel: {
|
||||
get: function () {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the comma-separated list of layer IDs to show.
|
||||
* @memberof ArcGisMapServerImageryProvider.prototype
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
layers: {
|
||||
get: function () {
|
||||
return this._layers;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an {@link ImageryProvider} which provides tiled imagery hosted by an ArcGIS MapServer. By default, the server's pre-cached tiles are
|
||||
* used, if available.
|
||||
*
|
||||
* @param {Resource|string} url The URL of the ArcGIS MapServer service.
|
||||
* @param {ArcGisMapServerImageryProvider.ConstructorOptions} [options] Object describing initialization options.
|
||||
* @returns {Promise<ArcGisMapServerImageryProvider>} A promise that resolves to the created ArcGisMapServerImageryProvider.
|
||||
*
|
||||
* @example
|
||||
* const esri = await Cesium.ArcGisMapServerImageryProvider.fromUrl(
|
||||
* "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer"
|
||||
* );
|
||||
*
|
||||
* @exception {RuntimeError} metadata spatial reference specifies an unknown WKID
|
||||
* @exception {RuntimeError} metadata fullExtent.spatialReference specifies an unknown WKID
|
||||
*/
|
||||
ArcGisMapServerImageryProvider.fromUrl = async function (url, options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("url", url);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
const resource = Resource.createIfNeeded(url);
|
||||
resource.appendForwardSlash();
|
||||
|
||||
if (defined(options.token)) {
|
||||
resource.setQueryParameters({
|
||||
token: options.token,
|
||||
});
|
||||
}
|
||||
|
||||
const provider = new ArcGisMapServerImageryProvider(options);
|
||||
provider._resource = resource;
|
||||
const imageryProviderBuilder = new ImageryProviderBuilder(options);
|
||||
const useTiles = options.usePreCachedTilesIfAvailable ?? true;
|
||||
if (useTiles) {
|
||||
await requestMetadata(resource, imageryProviderBuilder);
|
||||
}
|
||||
|
||||
imageryProviderBuilder.build(provider);
|
||||
return provider;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the credits to be displayed when a given tile is displayed.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level;
|
||||
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
|
||||
*/
|
||||
ArcGisMapServerImageryProvider.prototype.getTileCredits = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
) {
|
||||
return this._tileCredits;
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the image for a given tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {Request} [request] The request object. Intended for internal use only.
|
||||
* @returns {Promise<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
|
||||
* undefined if there are too many active requests to the server, and the request should be retried later.
|
||||
*/
|
||||
ArcGisMapServerImageryProvider.prototype.requestImage = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
request,
|
||||
) {
|
||||
return ImageryProvider.loadImage(
|
||||
this,
|
||||
buildImageResource(this, x, y, level, request),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
|
||||
* a tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {number} longitude The longitude at which to pick features.
|
||||
* @param {number} latitude The latitude at which to pick features.
|
||||
* @return {Promise<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
|
||||
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
|
||||
* instances. The array may be empty if no features are found at the given location.
|
||||
*/
|
||||
ArcGisMapServerImageryProvider.prototype.pickFeatures = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
longitude,
|
||||
latitude,
|
||||
) {
|
||||
if (!this.enablePickFeatures) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rectangle = this._tilingScheme.tileXYToNativeRectangle(x, y, level);
|
||||
|
||||
let horizontal;
|
||||
let vertical;
|
||||
let sr;
|
||||
if (this._tilingScheme.projection instanceof GeographicProjection) {
|
||||
horizontal = CesiumMath.toDegrees(longitude);
|
||||
vertical = CesiumMath.toDegrees(latitude);
|
||||
sr = "4326";
|
||||
} else {
|
||||
const projected = this._tilingScheme.projection.project(
|
||||
new Cartographic(longitude, latitude, 0.0),
|
||||
);
|
||||
horizontal = projected.x;
|
||||
vertical = projected.y;
|
||||
sr = "3857";
|
||||
}
|
||||
|
||||
let layers = "visible";
|
||||
if (defined(this._layers)) {
|
||||
layers += `:${this._layers}`;
|
||||
}
|
||||
|
||||
const query = {
|
||||
f: "json",
|
||||
tolerance: 2,
|
||||
geometryType: "esriGeometryPoint",
|
||||
geometry: `${horizontal},${vertical}`,
|
||||
mapExtent: `${rectangle.west},${rectangle.south},${rectangle.east},${rectangle.north}`,
|
||||
imageDisplay: `${this._tileWidth},${this._tileHeight},96`,
|
||||
sr: sr,
|
||||
layers: layers,
|
||||
};
|
||||
|
||||
const resource = this._resource.getDerivedResource({
|
||||
url: "identify",
|
||||
queryParameters: query,
|
||||
});
|
||||
|
||||
return resource.fetchJson().then(function (json) {
|
||||
const result = [];
|
||||
|
||||
const features = json.results;
|
||||
if (!defined(features)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (let i = 0; i < features.length; ++i) {
|
||||
const feature = features[i];
|
||||
|
||||
const featureInfo = new ImageryLayerFeatureInfo();
|
||||
featureInfo.data = feature;
|
||||
featureInfo.name = feature.value;
|
||||
featureInfo.properties = feature.attributes;
|
||||
featureInfo.configureDescriptionFromProperties(feature.attributes);
|
||||
|
||||
// If this is a point feature, use the coordinates of the point.
|
||||
if (feature.geometryType === "esriGeometryPoint" && feature.geometry) {
|
||||
const wkid =
|
||||
feature.geometry.spatialReference &&
|
||||
feature.geometry.spatialReference.wkid
|
||||
? feature.geometry.spatialReference.wkid
|
||||
: 4326;
|
||||
if (wkid === 4326 || wkid === 4283) {
|
||||
featureInfo.position = Cartographic.fromDegrees(
|
||||
feature.geometry.x,
|
||||
feature.geometry.y,
|
||||
feature.geometry.z,
|
||||
);
|
||||
} else if (wkid === 102100 || wkid === 900913 || wkid === 3857) {
|
||||
const projection = new WebMercatorProjection();
|
||||
featureInfo.position = projection.unproject(
|
||||
new Cartesian3(
|
||||
feature.geometry.x,
|
||||
feature.geometry.y,
|
||||
feature.geometry.z,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result.push(featureInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
};
|
||||
ArcGisMapServerImageryProvider._metadataCache = {};
|
||||
export default ArcGisMapServerImageryProvider;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import Credit from "../Core/Credit.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
|
||||
let defaultTokenCredit;
|
||||
const defaultAccessToken =
|
||||
"AAPTauP-rqeCeHTtrSMga9XbPxQ..ZMIZzf1tcwicoJi5E5T2mG0EO3qlIRkQNe5S-uA95RJKU0YkrwOT0AF-lSIyXXBFakDJIPaxsZ5vpnzlaW3kCYG2Tk53bY6OntMqn1EBYtDPyqz38iM4NU_bbDN3DuxUdkxYDWnxJXxxWe3bEeUANbvhKK76PU7N0wjZgtEYHYocspYXSHxbCEYo_eXrHDkrWpgkzcmQ3WpQqf2XprJ39pbqVSZbxskDh3zBm92XxUaiSmdf7rC1w3VoAT1_UarLJfAQ";
|
||||
/**
|
||||
* Default options for accessing the ArcGIS image tile service.
|
||||
*
|
||||
* An ArcGIS access token is required to access ArcGIS image tile layers.
|
||||
* A default token is provided for evaluation purposes only.
|
||||
* To obtain an access token, go to {@link https://developers.arcgis.com} and create a free account.
|
||||
* More info can be found in the {@link https://developers.arcgis.com/documentation/mapping-apis-and-services/security/ | ArcGIS developer guide}.
|
||||
*
|
||||
* @see ArcGisMapServerImageryProvider
|
||||
* @namespace ArcGisMapService
|
||||
*/
|
||||
|
||||
const ArcGisMapService = {};
|
||||
/**
|
||||
* Gets or sets the default ArcGIS access token.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
ArcGisMapService.defaultAccessToken = defaultAccessToken;
|
||||
|
||||
/**
|
||||
* Gets or sets the URL of the ArcGIS World Imagery tile service.
|
||||
*
|
||||
* @type {string|Resource}
|
||||
* @default https://ibasemaps-api.arcgis.com/arcgis/rest/services/World_Imagery/MapServer
|
||||
*/
|
||||
ArcGisMapService.defaultWorldImageryServer = new Resource({
|
||||
url: "https://ibasemaps-api.arcgis.com/arcgis/rest/services/World_Imagery/MapServer",
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets or sets the URL of the ArcGIS World Hillshade tile service.
|
||||
*
|
||||
* @type {string|Resource}
|
||||
* @default https://ibasemaps-api.arcgis.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer
|
||||
*/
|
||||
ArcGisMapService.defaultWorldHillshadeServer = new Resource({
|
||||
url: "https://ibasemaps-api.arcgis.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer",
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets or sets the URL of the ArcGIS World Oceans tile service.
|
||||
*
|
||||
* @type {string|Resource}
|
||||
* @default https://ibasemaps-api.arcgis.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer
|
||||
*/
|
||||
ArcGisMapService.defaultWorldOceanServer = new Resource({
|
||||
url: "https://ibasemaps-api.arcgis.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer",
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} providedKey
|
||||
* @return {string|undefined}
|
||||
*/
|
||||
ArcGisMapService.getDefaultTokenCredit = function (providedKey) {
|
||||
if (providedKey !== defaultAccessToken) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!defined(defaultTokenCredit)) {
|
||||
const defaultTokenMessage =
|
||||
'<b> \
|
||||
This application is using a default ArcGIS access token. Please assign <i>Cesium.ArcGisMapService.defaultAccessToken</i> \
|
||||
with an API key from your ArcGIS Developer account before using the ArcGIS tile services. \
|
||||
You can sign up for a free ArcGIS Developer account at <a href="https://developers.arcgis.com/">https://developers.arcgis.com/</a>.</b>';
|
||||
|
||||
defaultTokenCredit = new Credit(defaultTokenMessage, true);
|
||||
}
|
||||
|
||||
return defaultTokenCredit;
|
||||
};
|
||||
export default ArcGisMapService;
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import DynamicAtmosphereLightingType from "./DynamicAtmosphereLightingType.js";
|
||||
|
||||
/**
|
||||
* Common atmosphere settings used by 3D Tiles and models for rendering sky atmosphere, ground atmosphere, and fog.
|
||||
*
|
||||
* <p>
|
||||
* This class is not to be confused with {@link SkyAtmosphere}, which is responsible for rendering the sky.
|
||||
* </p>
|
||||
* <p>
|
||||
* While the atmosphere settings affect the color of fog, see {@link Fog} to control how fog is rendered.
|
||||
* </p>
|
||||
*
|
||||
* @alias Atmosphere
|
||||
* @constructor
|
||||
*
|
||||
* @example
|
||||
* // Turn on dynamic atmosphere lighting using the sun direction
|
||||
* scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SUNLIGHT;
|
||||
*
|
||||
* @example
|
||||
* // Turn on dynamic lighting using whatever light source is in the scene
|
||||
* scene.light = new Cesium.DirectionalLight({
|
||||
* direction: new Cesium.Cartesian3(1, 0, 0)
|
||||
* });
|
||||
* scene.atmosphere.dynamicLighting = Cesium.DynamicAtmosphereLightingType.SCENE_LIGHT;
|
||||
*
|
||||
* @example
|
||||
* // Adjust the color of the atmosphere effects.
|
||||
* scene.atmosphere.hueShift = 0.4; // Cycle 40% around the color wheel
|
||||
* scene.atmosphere.brightnessShift = 0.25; // Increase the brightness
|
||||
* scene.atmosphere.saturationShift = -0.1; // Desaturate the colors
|
||||
*
|
||||
* @see SkyAtmosphere
|
||||
* @see Globe
|
||||
* @see Fog
|
||||
*/
|
||||
function Atmosphere() {
|
||||
/**
|
||||
* The intensity of the light that is used for computing the ground atmosphere color.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 10.0
|
||||
*/
|
||||
this.lightIntensity = 10.0;
|
||||
|
||||
/**
|
||||
* The Rayleigh scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.
|
||||
*
|
||||
* @type {Cartesian3}
|
||||
* @default Cartesian3(5.5e-6, 13.0e-6, 28.4e-6)
|
||||
*/
|
||||
this.rayleighCoefficient = new Cartesian3(5.5e-6, 13.0e-6, 28.4e-6);
|
||||
|
||||
/**
|
||||
* The Mie scattering coefficient used in the atmospheric scattering equations for the ground atmosphere.
|
||||
*
|
||||
* @type {Cartesian3}
|
||||
* @default Cartesian3(21e-6, 21e-6, 21e-6)
|
||||
*/
|
||||
this.mieCoefficient = new Cartesian3(21e-6, 21e-6, 21e-6);
|
||||
|
||||
/**
|
||||
* The Rayleigh scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 10000.0
|
||||
*/
|
||||
this.rayleighScaleHeight = 10000.0;
|
||||
|
||||
/**
|
||||
* The Mie scale height used in the atmospheric scattering equations for the ground atmosphere, in meters.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 3200.0
|
||||
*/
|
||||
this.mieScaleHeight = 3200.0;
|
||||
|
||||
/**
|
||||
* The anisotropy of the medium to consider for Mie scattering.
|
||||
* <p>
|
||||
* Valid values are between -1.0 and 1.0.
|
||||
* </p>
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.9
|
||||
*/
|
||||
this.mieAnisotropy = 0.9;
|
||||
|
||||
/**
|
||||
* The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).
|
||||
* A hue shift of 1.0 indicates a complete rotation of the hues available.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.0
|
||||
*/
|
||||
this.hueShift = 0.0;
|
||||
|
||||
/**
|
||||
* The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).
|
||||
* A saturation shift of -1.0 is monochrome.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.0
|
||||
*/
|
||||
this.saturationShift = 0.0;
|
||||
|
||||
/**
|
||||
* The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).
|
||||
* A brightness shift of -1.0 is complete darkness, which will let space show through.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.0
|
||||
*/
|
||||
this.brightnessShift = 0.0;
|
||||
|
||||
/**
|
||||
* When not DynamicAtmosphereLightingType.NONE, the selected light source will
|
||||
* be used for dynamically lighting all atmosphere-related rendering effects.
|
||||
*
|
||||
* @type {DynamicAtmosphereLightingType}
|
||||
* @default DynamicAtmosphereLightingType.NONE
|
||||
*/
|
||||
this.dynamicLighting = DynamicAtmosphereLightingType.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the atmosphere shader requires a color correct step.
|
||||
* @param {Atmosphere} atmosphere The atmosphere instance to check
|
||||
* @returns {boolean} true if the atmosphere shader requires a color correct step
|
||||
*/
|
||||
Atmosphere.requiresColorCorrect = function (atmosphere) {
|
||||
return !(
|
||||
CesiumMath.equalsEpsilon(atmosphere.hueShift, 0.0, CesiumMath.EPSILON7) &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
atmosphere.saturationShift,
|
||||
0.0,
|
||||
CesiumMath.EPSILON7,
|
||||
) &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
atmosphere.brightnessShift,
|
||||
0.0,
|
||||
CesiumMath.EPSILON7,
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Atmosphere;
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Matrix2 from "../Core/Matrix2.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
|
||||
/**
|
||||
* An enum describing the attribute type for glTF and 3D Tiles.
|
||||
*
|
||||
* @enum {string}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const AttributeType = {
|
||||
/**
|
||||
* The attribute is a single component.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
SCALAR: "SCALAR",
|
||||
|
||||
/**
|
||||
* The attribute is a two-component vector.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
VEC2: "VEC2",
|
||||
|
||||
/**
|
||||
* The attribute is a three-component vector.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
VEC3: "VEC3",
|
||||
|
||||
/**
|
||||
* The attribute is a four-component vector.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
VEC4: "VEC4",
|
||||
|
||||
/**
|
||||
* The attribute is a 2x2 matrix.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
MAT2: "MAT2",
|
||||
|
||||
/**
|
||||
* The attribute is a 3x3 matrix.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
MAT3: "MAT3",
|
||||
|
||||
/**
|
||||
* The attribute is a 4x4 matrix.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
MAT4: "MAT4",
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the scalar, vector, or matrix type for the attribute type.
|
||||
*
|
||||
* @param {AttributeType} attributeType The attribute type.
|
||||
* @returns {*} The math type.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
AttributeType.getMathType = function (attributeType) {
|
||||
switch (attributeType) {
|
||||
case AttributeType.SCALAR:
|
||||
return Number;
|
||||
case AttributeType.VEC2:
|
||||
return Cartesian2;
|
||||
case AttributeType.VEC3:
|
||||
return Cartesian3;
|
||||
case AttributeType.VEC4:
|
||||
return Cartesian4;
|
||||
case AttributeType.MAT2:
|
||||
return Matrix2;
|
||||
case AttributeType.MAT3:
|
||||
return Matrix3;
|
||||
case AttributeType.MAT4:
|
||||
return Matrix4;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
default:
|
||||
throw new DeveloperError("attributeType is not a valid value.");
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the number of components per attribute.
|
||||
*
|
||||
* @param {AttributeType} attributeType The attribute type.
|
||||
* @returns {number} The number of components.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
AttributeType.getNumberOfComponents = function (attributeType) {
|
||||
switch (attributeType) {
|
||||
case AttributeType.SCALAR:
|
||||
return 1;
|
||||
case AttributeType.VEC2:
|
||||
return 2;
|
||||
case AttributeType.VEC3:
|
||||
return 3;
|
||||
case AttributeType.VEC4:
|
||||
case AttributeType.MAT2:
|
||||
return 4;
|
||||
case AttributeType.MAT3:
|
||||
return 9;
|
||||
case AttributeType.MAT4:
|
||||
return 16;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
default:
|
||||
throw new DeveloperError("attributeType is not a valid value.");
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of attribute locations needed to fit this attribute. Most
|
||||
* types require one, but matrices require multiple attribute locations.
|
||||
*
|
||||
* @param {AttributeType} attributeType The attribute type.
|
||||
* @returns {number} The number of attribute locations needed in the shader
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
AttributeType.getAttributeLocationCount = function (attributeType) {
|
||||
switch (attributeType) {
|
||||
case AttributeType.SCALAR:
|
||||
case AttributeType.VEC2:
|
||||
case AttributeType.VEC3:
|
||||
case AttributeType.VEC4:
|
||||
return 1;
|
||||
case AttributeType.MAT2:
|
||||
return 2;
|
||||
case AttributeType.MAT3:
|
||||
return 3;
|
||||
case AttributeType.MAT4:
|
||||
return 4;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
default:
|
||||
throw new DeveloperError("attributeType is not a valid value.");
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the GLSL type for the attribute type.
|
||||
*
|
||||
* @param {AttributeType} attributeType The attribute type.
|
||||
* @returns {string} The GLSL type for the attribute type.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
AttributeType.getGlslType = function (attributeType) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("attributeType", attributeType);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
switch (attributeType) {
|
||||
case AttributeType.SCALAR:
|
||||
return "float";
|
||||
case AttributeType.VEC2:
|
||||
return "vec2";
|
||||
case AttributeType.VEC3:
|
||||
return "vec3";
|
||||
case AttributeType.VEC4:
|
||||
return "vec4";
|
||||
case AttributeType.MAT2:
|
||||
return "mat2";
|
||||
case AttributeType.MAT3:
|
||||
return "mat3";
|
||||
case AttributeType.MAT4:
|
||||
return "mat4";
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
default:
|
||||
throw new DeveloperError("attributeType is not a valid value.");
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(AttributeType);
|
||||
|
||||
export default AttributeType;
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import ClearCommand from "../Renderer/ClearCommand.js";
|
||||
import FramebufferManager from "../Renderer/FramebufferManager.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
|
||||
/**
|
||||
* A post process stage that will get the luminance value at each pixel and
|
||||
* uses parallel reduction to compute the average luminance in a 1x1 texture.
|
||||
* This texture can be used as input for tone mapping.
|
||||
*
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function AutoExposure() {
|
||||
this._uniformMap = undefined;
|
||||
this._command = undefined;
|
||||
|
||||
this._colorTexture = undefined;
|
||||
this._depthTexture = undefined;
|
||||
|
||||
this._ready = false;
|
||||
|
||||
this._name = "czm_autoexposure";
|
||||
|
||||
this._logDepthChanged = undefined;
|
||||
this._useLogDepth = undefined;
|
||||
|
||||
this._framebuffers = undefined;
|
||||
this._previousLuminance = new FramebufferManager();
|
||||
|
||||
this._commands = undefined;
|
||||
this._clearCommand = undefined;
|
||||
|
||||
this._minMaxLuminance = new Cartesian2();
|
||||
|
||||
/**
|
||||
* Whether or not to execute this post-process stage when ready.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.enabled = true;
|
||||
this._enabled = true;
|
||||
|
||||
/**
|
||||
* The minimum value used to clamp the luminance.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.1
|
||||
*/
|
||||
this.minimumLuminance = 0.1;
|
||||
|
||||
/**
|
||||
* The maximum value used to clamp the luminance.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 10.0
|
||||
*/
|
||||
this.maximumLuminance = 10.0;
|
||||
}
|
||||
|
||||
Object.defineProperties(AutoExposure.prototype, {
|
||||
/**
|
||||
* Determines if this post-process stage is ready to be executed. A stage is only executed when both <code>ready</code>
|
||||
* and {@link AutoExposure#enabled} are <code>true</code>. A stage will not be ready while it is waiting on textures
|
||||
* to load.
|
||||
*
|
||||
* @memberof AutoExposure.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
ready: {
|
||||
get: function () {
|
||||
return this._ready;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The unique name of this post-process stage for reference by other stages.
|
||||
*
|
||||
* @memberof AutoExposure.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
name: {
|
||||
get: function () {
|
||||
return this._name;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A reference to the texture written to when executing this post process stage.
|
||||
*
|
||||
* @memberof AutoExposure.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
outputTexture: {
|
||||
get: function () {
|
||||
const framebuffers = this._framebuffers;
|
||||
if (!defined(framebuffers)) {
|
||||
return undefined;
|
||||
}
|
||||
return framebuffers[framebuffers.length - 1].getColorTexture(0);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function destroyFramebuffers(autoexposure) {
|
||||
const framebuffers = autoexposure._framebuffers;
|
||||
if (!defined(framebuffers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const length = framebuffers.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
framebuffers[i].destroy();
|
||||
}
|
||||
autoexposure._framebuffers = undefined;
|
||||
|
||||
autoexposure._previousLuminance.destroy();
|
||||
autoexposure._previousLuminance = undefined;
|
||||
}
|
||||
|
||||
function createFramebuffers(autoexposure, context) {
|
||||
destroyFramebuffers(autoexposure);
|
||||
|
||||
let width = autoexposure._width;
|
||||
let height = autoexposure._height;
|
||||
|
||||
const pixelDatatype = context.halfFloatingPointTexture
|
||||
? PixelDatatype.HALF_FLOAT
|
||||
: PixelDatatype.FLOAT;
|
||||
|
||||
const length = Math.ceil(Math.log(Math.max(width, height)) / Math.log(3.0));
|
||||
const framebuffers = new Array(length);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
width = Math.max(Math.ceil(width / 3.0), 1.0);
|
||||
height = Math.max(Math.ceil(height / 3.0), 1.0);
|
||||
framebuffers[i] = new FramebufferManager();
|
||||
framebuffers[i].update(context, width, height, 1, pixelDatatype);
|
||||
}
|
||||
|
||||
const lastTexture = framebuffers[length - 1].getColorTexture(0);
|
||||
autoexposure._previousLuminance.update(
|
||||
context,
|
||||
lastTexture.width,
|
||||
lastTexture.height,
|
||||
1,
|
||||
pixelDatatype,
|
||||
);
|
||||
autoexposure._framebuffers = framebuffers;
|
||||
}
|
||||
|
||||
function destroyCommands(autoexposure) {
|
||||
const commands = autoexposure._commands;
|
||||
if (!defined(commands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const length = commands.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
commands[i].shaderProgram.destroy();
|
||||
}
|
||||
autoexposure._commands = undefined;
|
||||
}
|
||||
|
||||
function createUniformMap(autoexposure, index) {
|
||||
let uniforms;
|
||||
if (index === 0) {
|
||||
uniforms = {
|
||||
colorTexture: function () {
|
||||
return autoexposure._colorTexture;
|
||||
},
|
||||
colorTextureDimensions: function () {
|
||||
return autoexposure._colorTexture.dimensions;
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const texture = autoexposure._framebuffers[index - 1].getColorTexture(0);
|
||||
uniforms = {
|
||||
colorTexture: function () {
|
||||
return texture;
|
||||
},
|
||||
colorTextureDimensions: function () {
|
||||
return texture.dimensions;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
uniforms.minMaxLuminance = function () {
|
||||
return autoexposure._minMaxLuminance;
|
||||
};
|
||||
uniforms.previousLuminance = function () {
|
||||
return autoexposure._previousLuminance.getColorTexture(0);
|
||||
};
|
||||
|
||||
return uniforms;
|
||||
}
|
||||
|
||||
function getShaderSource(index, length) {
|
||||
let source =
|
||||
"uniform sampler2D colorTexture; \n" +
|
||||
"in vec2 v_textureCoordinates; \n" +
|
||||
"float sampleTexture(vec2 offset) { \n";
|
||||
|
||||
if (index === 0) {
|
||||
source +=
|
||||
" vec4 color = texture(colorTexture, v_textureCoordinates + offset); \n" +
|
||||
" return czm_luminance(color.rgb); \n";
|
||||
} else {
|
||||
source +=
|
||||
" return texture(colorTexture, v_textureCoordinates + offset).r; \n";
|
||||
}
|
||||
|
||||
source += "}\n\n";
|
||||
|
||||
source +=
|
||||
"uniform vec2 colorTextureDimensions; \n" +
|
||||
"uniform vec2 minMaxLuminance; \n" +
|
||||
"uniform sampler2D previousLuminance; \n" +
|
||||
"void main() { \n" +
|
||||
" float color = 0.0; \n" +
|
||||
" float xStep = 1.0 / colorTextureDimensions.x; \n" +
|
||||
" float yStep = 1.0 / colorTextureDimensions.y; \n" +
|
||||
" int count = 0; \n" +
|
||||
" for (int i = 0; i < 3; ++i) { \n" +
|
||||
" for (int j = 0; j < 3; ++j) { \n" +
|
||||
" vec2 offset; \n" +
|
||||
" offset.x = -xStep + float(i) * xStep; \n" +
|
||||
" offset.y = -yStep + float(j) * yStep; \n" +
|
||||
" if (offset.x < 0.0 || offset.x > 1.0 || offset.y < 0.0 || offset.y > 1.0) { \n" +
|
||||
" continue; \n" +
|
||||
" } \n" +
|
||||
" color += sampleTexture(offset); \n" +
|
||||
" ++count; \n" +
|
||||
" } \n" +
|
||||
" } \n" +
|
||||
" if (count > 0) { \n" +
|
||||
" color /= float(count); \n" +
|
||||
" } \n";
|
||||
|
||||
if (index === length - 1) {
|
||||
source +=
|
||||
" float previous = texture(previousLuminance, vec2(0.5)).r; \n" +
|
||||
" color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n" +
|
||||
" color = previous + (color - previous) / (60.0 * 1.5); \n" +
|
||||
" color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n";
|
||||
}
|
||||
|
||||
source += " out_FragColor = vec4(color); \n" + "} \n";
|
||||
return source;
|
||||
}
|
||||
|
||||
function createCommands(autoexposure, context) {
|
||||
destroyCommands(autoexposure);
|
||||
const framebuffers = autoexposure._framebuffers;
|
||||
const length = framebuffers.length;
|
||||
|
||||
const commands = new Array(length);
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
commands[i] = context.createViewportQuadCommand(
|
||||
getShaderSource(i, length),
|
||||
{
|
||||
framebuffer: framebuffers[i].framebuffer,
|
||||
uniformMap: createUniformMap(autoexposure, i),
|
||||
},
|
||||
);
|
||||
}
|
||||
autoexposure._commands = commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that will be called before execute. Used to clear any textures attached to framebuffers.
|
||||
* @param {Context} context The context.
|
||||
* @private
|
||||
*/
|
||||
AutoExposure.prototype.clear = function (context) {
|
||||
const framebuffers = this._framebuffers;
|
||||
if (!defined(framebuffers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let clearCommand = this._clearCommand;
|
||||
if (!defined(clearCommand)) {
|
||||
clearCommand = this._clearCommand = new ClearCommand({
|
||||
color: new Color(0.0, 0.0, 0.0, 0.0),
|
||||
framebuffer: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const length = framebuffers.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
framebuffers[i].clear(context, clearCommand);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that will be called before execute. Used to create WebGL resources and load any textures.
|
||||
* @param {Context} context The context.
|
||||
* @private
|
||||
*/
|
||||
AutoExposure.prototype.update = function (context) {
|
||||
const width = context.drawingBufferWidth;
|
||||
const height = context.drawingBufferHeight;
|
||||
|
||||
if (width !== this._width || height !== this._height) {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
createFramebuffers(this, context);
|
||||
createCommands(this, context);
|
||||
|
||||
if (!this._ready) {
|
||||
this._ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._minMaxLuminance.x = this.minimumLuminance;
|
||||
this._minMaxLuminance.y = this.maximumLuminance;
|
||||
|
||||
const framebuffers = this._framebuffers;
|
||||
const temp = framebuffers[framebuffers.length - 1];
|
||||
framebuffers[framebuffers.length - 1] = this._previousLuminance;
|
||||
this._commands[this._commands.length - 1].framebuffer =
|
||||
this._previousLuminance.framebuffer;
|
||||
this._previousLuminance = temp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage.
|
||||
* @param {Context} context The context.
|
||||
* @param {Texture} colorTexture The input color texture.
|
||||
* @private
|
||||
*/
|
||||
AutoExposure.prototype.execute = function (context, colorTexture) {
|
||||
this._colorTexture = colorTexture;
|
||||
|
||||
const commands = this._commands;
|
||||
if (!defined(commands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const length = commands.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
commands[i].execute(context);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 AutoExposure#destroy
|
||||
*/
|
||||
AutoExposure.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.
|
||||
*
|
||||
* @see AutoExposure#isDestroyed
|
||||
*/
|
||||
AutoExposure.prototype.destroy = function () {
|
||||
destroyFramebuffers(this);
|
||||
destroyCommands(this);
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default AutoExposure;
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
|
||||
/**
|
||||
* An enum describing the x, y, and z axes and helper conversion functions.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const Axis = {
|
||||
/**
|
||||
* Denotes the x-axis.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
X: 0,
|
||||
|
||||
/**
|
||||
* Denotes the y-axis.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
Y: 1,
|
||||
|
||||
/**
|
||||
* Denotes the z-axis.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
Z: 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Matrix used to convert from y-up to z-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.Y_UP_TO_Z_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about PI/2 around the X-axis
|
||||
Matrix3.fromArray([1, 0, 0, 0, 0, 1, 0, -1, 0]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Matrix used to convert from z-up to y-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.Z_UP_TO_Y_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about -PI/2 around the X-axis
|
||||
Matrix3.fromArray([1, 0, 0, 0, 0, -1, 0, 1, 0]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Matrix used to convert from x-up to z-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.X_UP_TO_Z_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about -PI/2 around the Y-axis
|
||||
Matrix3.fromArray([0, 0, 1, 0, 1, 0, -1, 0, 0]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Matrix used to convert from z-up to x-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.Z_UP_TO_X_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about PI/2 around the Y-axis
|
||||
Matrix3.fromArray([0, 0, -1, 0, 1, 0, 1, 0, 0]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Matrix used to convert from x-up to y-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.X_UP_TO_Y_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about PI/2 around the Z-axis
|
||||
Matrix3.fromArray([0, 1, 0, -1, 0, 0, 0, 0, 1]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Matrix used to convert from y-up to x-up
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @constant
|
||||
*/
|
||||
Axis.Y_UP_TO_X_UP = Matrix4.fromRotationTranslation(
|
||||
// Rotation about -PI/2 around the Z-axis
|
||||
Matrix3.fromArray([0, -1, 0, 1, 0, 0, 0, 0, 1]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the axis by name
|
||||
*
|
||||
* @param {string} name The name of the axis.
|
||||
* @returns {number} The axis enum.
|
||||
*/
|
||||
Axis.fromName = function (name) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("name", name);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
return Axis[name];
|
||||
};
|
||||
|
||||
Object.freeze(Axis);
|
||||
|
||||
export default Axis;
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import IonResource from "../Core/IonResource.js";
|
||||
import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
|
||||
|
||||
const trailingSlashRegex = /\/$/;
|
||||
|
||||
/**
|
||||
* @typedef {object} Azure2DImageryProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the Azure2DImageryProvider constructor
|
||||
*
|
||||
* @property {string} subscriptionKey The public subscription key for the imagery.
|
||||
* @property {string} [url="https://atlas.microsoft.com/"] The Azure server url.
|
||||
* @property {string} [tilesetId="microsoft.imagery"] The Azure tileset ID. Valid options are {@link microsoft.imagery}, {@link microsoft.base.road}, and {@link microsoft.base.labels.road}
|
||||
* @property {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @property {number} [minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
|
||||
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
|
||||
* to result in rendering problems.
|
||||
* @property {number} [maximumLevel=22] The maximum level-of-detail supported by the imagery provider.
|
||||
* @property {Rectangle} [rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides 2D image tiles from Azure.
|
||||
*
|
||||
* @alias Azure2DImageryProvider
|
||||
* @constructor
|
||||
* @param {Azure2DImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||
*
|
||||
* @example
|
||||
* // Azure 2D imagery provider
|
||||
* const azureImageryProvider = new Cesium.Azure2DImageryProvider({
|
||||
* subscriptionKey: "subscription-key",
|
||||
* tilesetId: "microsoft.base.road"
|
||||
* });
|
||||
*/
|
||||
function Azure2DImageryProvider(options) {
|
||||
options = options ?? {};
|
||||
const tilesetId = options.tilesetId ?? "microsoft.imagery";
|
||||
this._maximumLevel = options.maximumLevel ?? 22;
|
||||
this._minimumLevel = options.minimumLevel ?? 0;
|
||||
|
||||
this._subscriptionKey =
|
||||
options.subscriptionKey ?? options["subscription-key"];
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("options.subscriptionKey", this._subscriptionKey);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._tilesetId = options.tilesetId;
|
||||
|
||||
const resource =
|
||||
options.url instanceof IonResource
|
||||
? options.url
|
||||
: Resource.createIfNeeded(options.url ?? "https://atlas.microsoft.com/");
|
||||
|
||||
let templateUrl = resource.getUrlComponent();
|
||||
if (!trailingSlashRegex.test(templateUrl)) {
|
||||
templateUrl += "/";
|
||||
}
|
||||
|
||||
const tilesUrl = `${templateUrl}map/tile`;
|
||||
this._viewportUrl = `${templateUrl}map/attribution`;
|
||||
|
||||
resource.url = tilesUrl;
|
||||
|
||||
resource.setQueryParameters({
|
||||
"api-version": "2024-04-01",
|
||||
tilesetId: tilesetId,
|
||||
"subscription-key": this._subscriptionKey,
|
||||
zoom: `{z}`,
|
||||
x: `{x}`,
|
||||
y: `{y}`,
|
||||
});
|
||||
|
||||
this._resource = resource;
|
||||
|
||||
let credit;
|
||||
if (defined(options.credit)) {
|
||||
credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
}
|
||||
|
||||
const provider = new UrlTemplateImageryProvider({
|
||||
...options,
|
||||
maximumLevel: this._maximumLevel,
|
||||
minimumLevel: this._minimumLevel,
|
||||
url: resource,
|
||||
credit: credit,
|
||||
});
|
||||
provider._resource = resource;
|
||||
this._imageryProvider = provider;
|
||||
|
||||
// This will be defined for ion resources
|
||||
this._tileCredits = resource.credits;
|
||||
this._attributionsByLevel = undefined;
|
||||
}
|
||||
|
||||
Object.defineProperties(Azure2DImageryProvider.prototype, {
|
||||
/**
|
||||
* Gets the URL of the Azure 2D Imagery server.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
url: {
|
||||
get: function () {
|
||||
return this._imageryProvider.url;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the rectangle, in radians, of the imagery provided by the instance.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Rectangle}
|
||||
* @readonly
|
||||
*/
|
||||
rectangle: {
|
||||
get: function () {
|
||||
return this._imageryProvider.rectangle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width of each tile, in pixels.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileWidth: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileWidth;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the height of each tile, in pixels.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileHeight: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileHeight;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum level-of-detail that can be requested.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.maximumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum level-of-detail that can be requested. Generally,
|
||||
* a minimum level should only be used when the rectangle of the imagery is small
|
||||
* enough that the number of tiles at the minimum level is small. An imagery
|
||||
* provider with more than a few tiles at the minimum level will lead to
|
||||
* rendering problems.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
minimumLevel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.minimumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tiling scheme used by the provider.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {TilingScheme}
|
||||
* @readonly
|
||||
*/
|
||||
tilingScheme: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tilingScheme;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tile discard policy. If not undefined, the discard policy is responsible
|
||||
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
|
||||
* returns undefined, no tiles are filtered.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {TileDiscardPolicy}
|
||||
* @readonly
|
||||
*/
|
||||
tileDiscardPolicy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.tileDiscardPolicy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an event that is raised when the imagery provider encounters an asynchronous error.. By subscribing
|
||||
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
|
||||
* are passed an instance of {@link TileProviderError}.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
errorEvent: {
|
||||
get: function () {
|
||||
return this._imageryProvider.errorEvent;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
|
||||
* the source of the imagery.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return this._imageryProvider.credit;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the proxy used by this provider.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {Proxy}
|
||||
* @readonly
|
||||
*/
|
||||
proxy: {
|
||||
get: function () {
|
||||
return this._imageryProvider.proxy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether or not the images provided by this imagery provider
|
||||
* include an alpha channel. If this property is false, an alpha channel, if present, will
|
||||
* be ignored. If this property is true, any images without an alpha channel will be treated
|
||||
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
|
||||
* and texture upload time are reduced.
|
||||
* @memberof Azure2DImageryProvider.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
hasAlphaChannel: {
|
||||
get: function () {
|
||||
return this._imageryProvider.hasAlphaChannel;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the credits to be displayed when a given tile is displayed.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level;
|
||||
* @returns {Credit[]|undefined} The credits to be displayed when the tile is displayed.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
||||
const hasAttributions = defined(this._attributionsByLevel);
|
||||
|
||||
if (!hasAttributions || !defined(this._tileCredits)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const innerCredits = this._attributionsByLevel.get(level);
|
||||
if (!defined(this._tileCredits)) {
|
||||
return innerCredits;
|
||||
}
|
||||
|
||||
return this._tileCredits.concat(innerCredits);
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the image for a given tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {Request} [request] The request object. Intended for internal use only.
|
||||
* @returns {Promise<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
|
||||
* undefined if there are too many active requests to the server, and the request should be retried later.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.requestImage = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
request,
|
||||
) {
|
||||
const promise = this._imageryProvider.requestImage(x, y, level, request);
|
||||
|
||||
// If the requestImage call returns undefined, it couldn't be scheduled this frame. Make sure to return undefined so this can be handled upstream.
|
||||
if (!defined(promise)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Asynchronously request and populate _attributionsByLevel if it hasn't been already. We do this here so that the promise can be properly awaited.
|
||||
if (!defined(this._attributionsByLevel)) {
|
||||
return Promise.all([promise, this.getViewportCredits()]).then(
|
||||
(results) => results[0],
|
||||
);
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Picking features is not currently supported by this imagery provider, so this function simply returns
|
||||
* undefined.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {number} longitude The longitude at which to pick features.
|
||||
* @param {number} latitude The latitude at which to pick features.
|
||||
* @return {undefined} Undefined since picking is not supported.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.pickFeatures = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
longitude,
|
||||
latitude,
|
||||
) {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get attribution for imagery from Azure Maps to display in the credits
|
||||
* @private
|
||||
* @return {Promise<Map<Credit[]>>} The list of attribution sources to display in the credits.
|
||||
*/
|
||||
Azure2DImageryProvider.prototype.getViewportCredits = async function () {
|
||||
const maximumLevel = this._maximumLevel;
|
||||
|
||||
const promises = [];
|
||||
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||
promises.push(
|
||||
fetchViewportAttribution(
|
||||
this._resource,
|
||||
this._viewportUrl,
|
||||
this._subscriptionKey,
|
||||
this._tilesetId,
|
||||
level,
|
||||
),
|
||||
);
|
||||
}
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
const attributionsByLevel = new Map();
|
||||
for (let level = 0; level < maximumLevel + 1; level++) {
|
||||
const credits = [];
|
||||
const attributions = results[level].join(",");
|
||||
if (attributions) {
|
||||
const levelCredits = new Credit(attributions);
|
||||
credits.push(levelCredits);
|
||||
}
|
||||
attributionsByLevel.set(level, credits);
|
||||
}
|
||||
|
||||
this._attributionsByLevel = attributionsByLevel;
|
||||
|
||||
return attributionsByLevel;
|
||||
};
|
||||
|
||||
async function fetchViewportAttribution(resource, url, key, tilesetId, level) {
|
||||
const viewportResource = resource.getDerivedResource({
|
||||
url,
|
||||
queryParameters: {
|
||||
zoom: level,
|
||||
bounds: "-180,-90,180,90",
|
||||
},
|
||||
data: JSON.stringify(Frozen.EMPTY_OBJECT),
|
||||
});
|
||||
|
||||
const viewportJson = await viewportResource.fetchJson();
|
||||
return viewportJson.copyrights;
|
||||
}
|
||||
|
||||
// Exposed for tests
|
||||
export default Azure2DImageryProvider;
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import deprecationWarning from "../Core/deprecationWarning.js";
|
||||
import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
|
||||
/**
|
||||
* Handles parsing of a Batched 3D Model.
|
||||
*
|
||||
* @namespace B3dmParser
|
||||
* @private
|
||||
*/
|
||||
const B3dmParser = {};
|
||||
B3dmParser._deprecationWarning = deprecationWarning;
|
||||
|
||||
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
|
||||
|
||||
/**
|
||||
* Parses the contents of a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel|Batched 3D Model}.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {ArrayBuffer} arrayBuffer The array buffer containing the b3dm.
|
||||
* @param {number} [byteOffset=0] The byte offset of the beginning of the b3dm in the array buffer.
|
||||
* @returns {object} Returns an object with the batch length, feature table (binary and json), batch table (binary and json) and glTF parts of the b3dm.
|
||||
*/
|
||||
B3dmParser.parse = function (arrayBuffer, byteOffset) {
|
||||
const byteStart = byteOffset ?? 0;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("arrayBuffer", arrayBuffer);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
byteOffset = byteStart;
|
||||
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const view = new DataView(arrayBuffer);
|
||||
byteOffset += sizeOfUint32; // Skip magic
|
||||
|
||||
const version = view.getUint32(byteOffset, true);
|
||||
if (version !== 1) {
|
||||
throw new RuntimeError(
|
||||
`Only Batched 3D Model version 1 is supported. Version ${version} is not.`,
|
||||
);
|
||||
}
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
const byteLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
let featureTableJsonByteLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
let featureTableBinaryByteLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
let batchTableJsonByteLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
let batchTableBinaryByteLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
let batchLength;
|
||||
|
||||
// Legacy header #1: [batchLength] [batchTableByteLength]
|
||||
// Legacy header #2: [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]
|
||||
// Current header: [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength]
|
||||
// If the header is in the first legacy format 'batchTableJsonByteLength' will be the start of the JSON string (a quotation mark) or the glTF magic.
|
||||
// Accordingly its first byte will be either 0x22 or 0x67, and so the minimum uint32 expected is 0x22000000 = 570425344 = 570MB. It is unlikely that the feature table JSON will exceed this length.
|
||||
// The check for the second legacy format is similar, except it checks 'batchTableBinaryByteLength' instead
|
||||
if (batchTableJsonByteLength >= 570425344) {
|
||||
// First legacy check
|
||||
byteOffset -= sizeOfUint32 * 2;
|
||||
batchLength = featureTableJsonByteLength;
|
||||
batchTableJsonByteLength = featureTableBinaryByteLength;
|
||||
batchTableBinaryByteLength = 0;
|
||||
featureTableJsonByteLength = 0;
|
||||
featureTableBinaryByteLength = 0;
|
||||
B3dmParser._deprecationWarning(
|
||||
"b3dm-legacy-header",
|
||||
"This b3dm header is using the legacy format [batchLength] [batchTableByteLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel.",
|
||||
);
|
||||
} else if (batchTableBinaryByteLength >= 570425344) {
|
||||
// Second legacy check
|
||||
byteOffset -= sizeOfUint32;
|
||||
batchLength = batchTableJsonByteLength;
|
||||
batchTableJsonByteLength = featureTableJsonByteLength;
|
||||
batchTableBinaryByteLength = featureTableBinaryByteLength;
|
||||
featureTableJsonByteLength = 0;
|
||||
featureTableBinaryByteLength = 0;
|
||||
B3dmParser._deprecationWarning(
|
||||
"b3dm-legacy-header",
|
||||
"This b3dm header is using the legacy format [batchTableJsonByteLength] [batchTableBinaryByteLength] [batchLength]. The new format is [featureTableJsonByteLength] [featureTableBinaryByteLength] [batchTableJsonByteLength] [batchTableBinaryByteLength] from https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Batched3DModel.",
|
||||
);
|
||||
}
|
||||
|
||||
let featureTableJson;
|
||||
if (featureTableJsonByteLength === 0) {
|
||||
featureTableJson = {
|
||||
BATCH_LENGTH: batchLength ?? 0,
|
||||
};
|
||||
} else {
|
||||
featureTableJson = getJsonFromTypedArray(
|
||||
uint8Array,
|
||||
byteOffset,
|
||||
featureTableJsonByteLength,
|
||||
);
|
||||
byteOffset += featureTableJsonByteLength;
|
||||
}
|
||||
|
||||
const featureTableBinary = new Uint8Array(
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
featureTableBinaryByteLength,
|
||||
);
|
||||
byteOffset += featureTableBinaryByteLength;
|
||||
|
||||
let batchTableJson;
|
||||
let batchTableBinary;
|
||||
if (batchTableJsonByteLength > 0) {
|
||||
// PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the
|
||||
// arraybuffer/string compressed in memory and then decompress it when it is first accessed.
|
||||
//
|
||||
// We could also make another request for it, but that would make the property set/get
|
||||
// API async, and would double the number of numbers in some cases.
|
||||
batchTableJson = getJsonFromTypedArray(
|
||||
uint8Array,
|
||||
byteOffset,
|
||||
batchTableJsonByteLength,
|
||||
);
|
||||
byteOffset += batchTableJsonByteLength;
|
||||
|
||||
if (batchTableBinaryByteLength > 0) {
|
||||
// Has a batch table binary
|
||||
batchTableBinary = new Uint8Array(
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
batchTableBinaryByteLength,
|
||||
);
|
||||
// Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
|
||||
batchTableBinary = new Uint8Array(batchTableBinary);
|
||||
byteOffset += batchTableBinaryByteLength;
|
||||
}
|
||||
}
|
||||
|
||||
const gltfByteLength = byteStart + byteLength - byteOffset;
|
||||
if (gltfByteLength === 0) {
|
||||
throw new RuntimeError("glTF byte length must be greater than 0.");
|
||||
}
|
||||
|
||||
let gltfView;
|
||||
if (byteOffset % 4 === 0) {
|
||||
gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength);
|
||||
} else {
|
||||
// Create a copy of the glb so that it is 4-byte aligned
|
||||
B3dmParser._deprecationWarning(
|
||||
"b3dm-glb-unaligned",
|
||||
"The embedded glb is not aligned to a 4-byte boundary.",
|
||||
);
|
||||
gltfView = new Uint8Array(
|
||||
uint8Array.subarray(byteOffset, byteOffset + gltfByteLength),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
batchLength: batchLength,
|
||||
featureTableJson: featureTableJson,
|
||||
featureTableBinary: featureTableBinary,
|
||||
batchTableJson: batchTableJson,
|
||||
batchTableBinary: batchTableBinary,
|
||||
gltf: gltfView,
|
||||
};
|
||||
};
|
||||
|
||||
export default B3dmParser;
|
||||
+636
@@ -0,0 +1,636 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import combine from "../Core/combine.js";
|
||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import PixelFormat from "../Core/PixelFormat.js";
|
||||
import ContextLimits from "../Renderer/ContextLimits.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
import Sampler from "../Renderer/Sampler.js";
|
||||
import Texture from "../Renderer/Texture.js";
|
||||
|
||||
/**
|
||||
* Creates a texture to look up per instance attributes for batched primitives. For example, store each primitive's pick color in the texture.
|
||||
*
|
||||
* @alias BatchTable
|
||||
* @constructor
|
||||
* @private
|
||||
*
|
||||
* @param {Context} context The context in which the batch table is created.
|
||||
* @param {object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name
|
||||
* to retrieve the value in the vertex shader.
|
||||
* @param {number} numberOfInstances The number of instances in a batch table.
|
||||
*
|
||||
* @example
|
||||
* // create the batch table
|
||||
* const attributes = [{
|
||||
* functionName : 'getShow',
|
||||
* componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
|
||||
* componentsPerAttribute : 1
|
||||
* }, {
|
||||
* functionName : 'getPickColor',
|
||||
* componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
|
||||
* componentsPerAttribute : 4,
|
||||
* normalize : true
|
||||
* }];
|
||||
* const batchTable = new BatchTable(context, attributes, 5);
|
||||
*
|
||||
* // when creating the draw commands, update the uniform map and the vertex shader
|
||||
* vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource);
|
||||
* const shaderProgram = ShaderProgram.fromCache({
|
||||
* // ...
|
||||
* vertexShaderSource : vertexShaderSource,
|
||||
* });
|
||||
*
|
||||
* drawCommand.shaderProgram = shaderProgram;
|
||||
* drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap);
|
||||
*
|
||||
* // use the attribute function names in the shader to retrieve the instance values
|
||||
* // ...
|
||||
* attribute float batchId;
|
||||
*
|
||||
* void main() {
|
||||
* // ...
|
||||
* float show = getShow(batchId);
|
||||
* vec3 pickColor = getPickColor(batchId);
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
function BatchTable(context, attributes, numberOfInstances) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(context)) {
|
||||
throw new DeveloperError("context is required");
|
||||
}
|
||||
if (!defined(attributes)) {
|
||||
throw new DeveloperError("attributes is required");
|
||||
}
|
||||
if (!defined(numberOfInstances)) {
|
||||
throw new DeveloperError("numberOfInstances is required");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._attributes = attributes;
|
||||
this._numberOfInstances = numberOfInstances;
|
||||
|
||||
if (attributes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PERFORMANCE_IDEA: We may be able to arrange the attributes so they can be packing into fewer texels.
|
||||
// Right now, an attribute with one component uses an entire texel when 4 single component attributes can
|
||||
// be packed into a texel.
|
||||
//
|
||||
// Packing floats into unsigned byte textures makes the problem worse. A single component float attribute
|
||||
// will be packed into a single texel leaving 3 texels unused. 4 texels are reserved for each float attribute
|
||||
// regardless of how many components it has.
|
||||
const pixelDatatype = getDatatype(attributes);
|
||||
const textureFloatSupported = context.floatingPointTexture;
|
||||
const packFloats =
|
||||
pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported;
|
||||
const offsets = createOffsets(attributes, packFloats);
|
||||
|
||||
const stride = getStride(offsets, attributes, packFloats);
|
||||
const maxNumberOfInstancesPerRow = Math.floor(
|
||||
ContextLimits.maximumTextureSize / stride,
|
||||
);
|
||||
|
||||
const instancesPerWidth = Math.min(
|
||||
numberOfInstances,
|
||||
maxNumberOfInstancesPerRow,
|
||||
);
|
||||
const width = stride * instancesPerWidth;
|
||||
const height = Math.ceil(numberOfInstances / instancesPerWidth);
|
||||
|
||||
const stepX = 1.0 / width;
|
||||
const centerX = stepX * 0.5;
|
||||
const stepY = 1.0 / height;
|
||||
const centerY = stepY * 0.5;
|
||||
|
||||
this._textureDimensions = new Cartesian2(width, height);
|
||||
this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
|
||||
this._pixelDatatype = !packFloats
|
||||
? pixelDatatype
|
||||
: PixelDatatype.UNSIGNED_BYTE;
|
||||
this._packFloats = packFloats;
|
||||
this._offsets = offsets;
|
||||
this._stride = stride;
|
||||
this._texture = undefined;
|
||||
|
||||
const batchLength = 4 * width * height;
|
||||
this._batchValues =
|
||||
pixelDatatype === PixelDatatype.FLOAT && !packFloats
|
||||
? new Float32Array(batchLength)
|
||||
: new Uint8Array(batchLength);
|
||||
this._batchValuesDirty = false;
|
||||
}
|
||||
|
||||
Object.defineProperties(BatchTable.prototype, {
|
||||
/**
|
||||
* The attribute descriptions.
|
||||
* @memberOf BatchTable.prototype
|
||||
* @type {object[]}
|
||||
* @readonly
|
||||
*/
|
||||
attributes: {
|
||||
get: function () {
|
||||
return this._attributes;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The number of instances.
|
||||
* @memberOf BatchTable.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
numberOfInstances: {
|
||||
get: function () {
|
||||
return this._numberOfInstances;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function getDatatype(attributes) {
|
||||
let foundFloatDatatype = false;
|
||||
const length = attributes.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (attributes[i].componentDatatype !== ComponentDatatype.UNSIGNED_BYTE) {
|
||||
foundFloatDatatype = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return foundFloatDatatype ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
function getAttributeType(attributes, attributeIndex) {
|
||||
const componentsPerAttribute =
|
||||
attributes[attributeIndex].componentsPerAttribute;
|
||||
if (componentsPerAttribute === 2) {
|
||||
return Cartesian2;
|
||||
} else if (componentsPerAttribute === 3) {
|
||||
return Cartesian3;
|
||||
} else if (componentsPerAttribute === 4) {
|
||||
return Cartesian4;
|
||||
}
|
||||
return Number;
|
||||
}
|
||||
|
||||
function createOffsets(attributes, packFloats) {
|
||||
const offsets = new Array(attributes.length);
|
||||
|
||||
let currentOffset = 0;
|
||||
const attributesLength = attributes.length;
|
||||
for (let i = 0; i < attributesLength; ++i) {
|
||||
const attribute = attributes[i];
|
||||
const componentDatatype = attribute.componentDatatype;
|
||||
|
||||
offsets[i] = currentOffset;
|
||||
|
||||
if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
|
||||
currentOffset += 4;
|
||||
} else {
|
||||
++currentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
function getStride(offsets, attributes, packFloats) {
|
||||
const length = offsets.length;
|
||||
const lastOffset = offsets[length - 1];
|
||||
const lastAttribute = attributes[length - 1];
|
||||
const componentDatatype = lastAttribute.componentDatatype;
|
||||
|
||||
if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
|
||||
return lastOffset + 4;
|
||||
}
|
||||
return lastOffset + 1;
|
||||
}
|
||||
|
||||
const scratchPackedFloatCartesian4 = new Cartesian4();
|
||||
|
||||
function getPackedFloat(array, index, result) {
|
||||
let packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4);
|
||||
const x = Cartesian4.unpackFloat(packed);
|
||||
|
||||
packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4);
|
||||
const y = Cartesian4.unpackFloat(packed);
|
||||
|
||||
packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4);
|
||||
const z = Cartesian4.unpackFloat(packed);
|
||||
|
||||
packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4);
|
||||
const w = Cartesian4.unpackFloat(packed);
|
||||
|
||||
return Cartesian4.fromElements(x, y, z, w, result);
|
||||
}
|
||||
|
||||
function setPackedAttribute(value, array, index) {
|
||||
let packed = Cartesian4.packFloat(value.x, scratchPackedFloatCartesian4);
|
||||
Cartesian4.pack(packed, array, index);
|
||||
|
||||
packed = Cartesian4.packFloat(value.y, packed);
|
||||
Cartesian4.pack(packed, array, index + 4);
|
||||
|
||||
packed = Cartesian4.packFloat(value.z, packed);
|
||||
Cartesian4.pack(packed, array, index + 8);
|
||||
|
||||
packed = Cartesian4.packFloat(value.w, packed);
|
||||
Cartesian4.pack(packed, array, index + 12);
|
||||
}
|
||||
|
||||
const scratchGetAttributeCartesian4 = new Cartesian4();
|
||||
|
||||
/**
|
||||
* Gets the value of an attribute in the table.
|
||||
*
|
||||
* @param {number} instanceIndex The index of the instance.
|
||||
* @param {number} attributeIndex The index of the attribute.
|
||||
* @param {undefined|Cartesian2|Cartesian3|Cartesian4} [result] The object onto which to store the result. The type is dependent on the attribute's number of components.
|
||||
* @returns {number|Cartesian2|Cartesian3|Cartesian4} The attribute value stored for the instance.
|
||||
*
|
||||
* @exception {DeveloperError} instanceIndex is out of range.
|
||||
* @exception {DeveloperError} attributeIndex is out of range.
|
||||
*/
|
||||
BatchTable.prototype.getBatchedAttribute = function (
|
||||
instanceIndex,
|
||||
attributeIndex,
|
||||
result,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
|
||||
throw new DeveloperError("instanceIndex is out of range.");
|
||||
}
|
||||
if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
|
||||
throw new DeveloperError("attributeIndex is out of range");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const attributes = this._attributes;
|
||||
const offset = this._offsets[attributeIndex];
|
||||
const stride = this._stride;
|
||||
|
||||
const index = 4 * stride * instanceIndex + 4 * offset;
|
||||
let value;
|
||||
|
||||
if (
|
||||
this._packFloats &&
|
||||
attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE
|
||||
) {
|
||||
value = getPackedFloat(
|
||||
this._batchValues,
|
||||
index,
|
||||
scratchGetAttributeCartesian4,
|
||||
);
|
||||
} else {
|
||||
value = Cartesian4.unpack(
|
||||
this._batchValues,
|
||||
index,
|
||||
scratchGetAttributeCartesian4,
|
||||
);
|
||||
}
|
||||
|
||||
const attributeType = getAttributeType(attributes, attributeIndex);
|
||||
if (defined(attributeType.fromCartesian4)) {
|
||||
return attributeType.fromCartesian4(value, result);
|
||||
} else if (defined(attributeType.clone)) {
|
||||
return attributeType.clone(value, result);
|
||||
}
|
||||
|
||||
return value.x;
|
||||
};
|
||||
|
||||
const setAttributeScratchValues = [
|
||||
undefined,
|
||||
undefined,
|
||||
new Cartesian2(),
|
||||
new Cartesian3(),
|
||||
new Cartesian4(),
|
||||
];
|
||||
const setAttributeScratchCartesian4 = new Cartesian4();
|
||||
|
||||
/**
|
||||
* Sets the value of an attribute in the table.
|
||||
*
|
||||
* @param {number} instanceIndex The index of the instance.
|
||||
* @param {number} attributeIndex The index of the attribute.
|
||||
* @param {number|Cartesian2|Cartesian3|Cartesian4} value The value to be stored in the table. The type of value will depend on the number of components of the attribute.
|
||||
*
|
||||
* @exception {DeveloperError} instanceIndex is out of range.
|
||||
* @exception {DeveloperError} attributeIndex is out of range.
|
||||
*/
|
||||
BatchTable.prototype.setBatchedAttribute = function (
|
||||
instanceIndex,
|
||||
attributeIndex,
|
||||
value,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
|
||||
throw new DeveloperError("instanceIndex is out of range.");
|
||||
}
|
||||
if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
|
||||
throw new DeveloperError("attributeIndex is out of range");
|
||||
}
|
||||
if (!defined(value)) {
|
||||
throw new DeveloperError("value is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const attributes = this._attributes;
|
||||
const result =
|
||||
setAttributeScratchValues[
|
||||
attributes[attributeIndex].componentsPerAttribute
|
||||
];
|
||||
const currentAttribute = this.getBatchedAttribute(
|
||||
instanceIndex,
|
||||
attributeIndex,
|
||||
result,
|
||||
);
|
||||
const attributeType = getAttributeType(this._attributes, attributeIndex);
|
||||
const entriesEqual = defined(attributeType.equals)
|
||||
? attributeType.equals(currentAttribute, value)
|
||||
: currentAttribute === value;
|
||||
if (entriesEqual) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attributeValue = setAttributeScratchCartesian4;
|
||||
attributeValue.x = defined(value.x) ? value.x : value;
|
||||
attributeValue.y = defined(value.y) ? value.y : 0.0;
|
||||
attributeValue.z = defined(value.z) ? value.z : 0.0;
|
||||
attributeValue.w = defined(value.w) ? value.w : 0.0;
|
||||
|
||||
const offset = this._offsets[attributeIndex];
|
||||
const stride = this._stride;
|
||||
const index = 4 * stride * instanceIndex + 4 * offset;
|
||||
|
||||
if (
|
||||
this._packFloats &&
|
||||
attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE
|
||||
) {
|
||||
setPackedAttribute(attributeValue, this._batchValues, index);
|
||||
} else {
|
||||
Cartesian4.pack(attributeValue, this._batchValues, index);
|
||||
}
|
||||
|
||||
this._batchValuesDirty = true;
|
||||
};
|
||||
|
||||
function createTexture(batchTable, context) {
|
||||
const dimensions = batchTable._textureDimensions;
|
||||
batchTable._texture = new Texture({
|
||||
context: context,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: batchTable._pixelDatatype,
|
||||
width: dimensions.x,
|
||||
height: dimensions.y,
|
||||
sampler: Sampler.NEAREST,
|
||||
flipY: false,
|
||||
});
|
||||
}
|
||||
|
||||
function updateTexture(batchTable) {
|
||||
const dimensions = batchTable._textureDimensions;
|
||||
batchTable._texture.copyFrom({
|
||||
source: {
|
||||
width: dimensions.x,
|
||||
height: dimensions.y,
|
||||
arrayBufferView: batchTable._batchValues,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates/updates the batch table texture.
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*
|
||||
* @exception {RuntimeError} The floating point texture extension is required but not supported.
|
||||
*/
|
||||
BatchTable.prototype.update = function (frameState) {
|
||||
if (
|
||||
(defined(this._texture) && !this._batchValuesDirty) ||
|
||||
this._attributes.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._batchValuesDirty = false;
|
||||
|
||||
if (!defined(this._texture)) {
|
||||
createTexture(this, frameState.context);
|
||||
}
|
||||
updateTexture(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a function that will update a uniform map to contain values for looking up values in the batch table.
|
||||
*
|
||||
* @returns {BatchTable.updateUniformMapCallback} A callback for updating uniform maps.
|
||||
*/
|
||||
BatchTable.prototype.getUniformMapCallback = function () {
|
||||
const that = this;
|
||||
return function (uniformMap) {
|
||||
if (that._attributes.length === 0) {
|
||||
return uniformMap;
|
||||
}
|
||||
|
||||
const batchUniformMap = {
|
||||
batchTexture: function () {
|
||||
return that._texture;
|
||||
},
|
||||
batchTextureDimensions: function () {
|
||||
return that._textureDimensions;
|
||||
},
|
||||
batchTextureStep: function () {
|
||||
return that._textureStep;
|
||||
},
|
||||
};
|
||||
return combine(uniformMap, batchUniformMap);
|
||||
};
|
||||
};
|
||||
|
||||
function getGlslComputeSt(batchTable) {
|
||||
const stride = batchTable._stride;
|
||||
|
||||
// GLSL batchId is zero-based: [0, numberOfInstances - 1]
|
||||
if (batchTable._textureDimensions.y === 1) {
|
||||
return (
|
||||
`${
|
||||
"uniform vec4 batchTextureStep; \n" +
|
||||
"vec2 computeSt(float batchId) \n" +
|
||||
"{ \n" +
|
||||
" float stepX = batchTextureStep.x; \n" +
|
||||
" float centerX = batchTextureStep.y; \n" +
|
||||
" float numberOfAttributes = float("
|
||||
}${stride}); \n` +
|
||||
` return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n` +
|
||||
`} \n`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
`${
|
||||
"uniform vec4 batchTextureStep; \n" +
|
||||
"uniform vec2 batchTextureDimensions; \n" +
|
||||
"vec2 computeSt(float batchId) \n" +
|
||||
"{ \n" +
|
||||
" float stepX = batchTextureStep.x; \n" +
|
||||
" float centerX = batchTextureStep.y; \n" +
|
||||
" float stepY = batchTextureStep.z; \n" +
|
||||
" float centerY = batchTextureStep.w; \n" +
|
||||
" float numberOfAttributes = float("
|
||||
}${stride}); \n` +
|
||||
` float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n` +
|
||||
` float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n` +
|
||||
` return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n` +
|
||||
`} \n`
|
||||
);
|
||||
}
|
||||
|
||||
function getComponentType(componentsPerAttribute) {
|
||||
if (componentsPerAttribute === 1) {
|
||||
return "float";
|
||||
}
|
||||
return `vec${componentsPerAttribute}`;
|
||||
}
|
||||
|
||||
function getComponentSwizzle(componentsPerAttribute) {
|
||||
if (componentsPerAttribute === 1) {
|
||||
return ".x";
|
||||
} else if (componentsPerAttribute === 2) {
|
||||
return ".xy";
|
||||
} else if (componentsPerAttribute === 3) {
|
||||
return ".xyz";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getGlslAttributeFunction(batchTable, attributeIndex) {
|
||||
const attributes = batchTable._attributes;
|
||||
const attribute = attributes[attributeIndex];
|
||||
const componentsPerAttribute = attribute.componentsPerAttribute;
|
||||
const functionName = attribute.functionName;
|
||||
const functionReturnType = getComponentType(componentsPerAttribute);
|
||||
const functionReturnValue = getComponentSwizzle(componentsPerAttribute);
|
||||
|
||||
const offset = batchTable._offsets[attributeIndex];
|
||||
|
||||
let glslFunction =
|
||||
`${functionReturnType} ${functionName}(float batchId) \n` +
|
||||
`{ \n` +
|
||||
` vec2 st = computeSt(batchId); \n` +
|
||||
` st.x += batchTextureStep.x * float(${offset}); \n`;
|
||||
|
||||
if (
|
||||
batchTable._packFloats &&
|
||||
attribute.componentDatatype !== PixelDatatype.UNSIGNED_BYTE
|
||||
) {
|
||||
glslFunction +=
|
||||
"vec4 textureValue; \n" +
|
||||
"textureValue.x = czm_unpackFloat(texture(batchTexture, st)); \n" +
|
||||
"textureValue.y = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x, 0.0))); \n" +
|
||||
"textureValue.z = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x * 2.0, 0.0))); \n" +
|
||||
"textureValue.w = czm_unpackFloat(texture(batchTexture, st + vec2(batchTextureStep.x * 3.0, 0.0))); \n";
|
||||
} else {
|
||||
glslFunction += " vec4 textureValue = texture(batchTexture, st); \n";
|
||||
}
|
||||
|
||||
glslFunction += ` ${functionReturnType} value = textureValue${functionReturnValue}; \n`;
|
||||
|
||||
if (
|
||||
batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE &&
|
||||
attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE &&
|
||||
!attribute.normalize
|
||||
) {
|
||||
glslFunction += "value *= 255.0; \n";
|
||||
} else if (
|
||||
batchTable._pixelDatatype === PixelDatatype.FLOAT &&
|
||||
attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE &&
|
||||
attribute.normalize
|
||||
) {
|
||||
glslFunction += "value /= 255.0; \n";
|
||||
}
|
||||
|
||||
glslFunction += " return value; \n" + "} \n";
|
||||
return glslFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that will update a vertex shader to contain functions for looking up values in the batch table.
|
||||
*
|
||||
* @returns {BatchTable.updateVertexShaderSourceCallback} A callback for updating a vertex shader source.
|
||||
*/
|
||||
BatchTable.prototype.getVertexShaderCallback = function () {
|
||||
const attributes = this._attributes;
|
||||
if (attributes.length === 0) {
|
||||
return function (source) {
|
||||
return source;
|
||||
};
|
||||
}
|
||||
|
||||
let batchTableShader = "uniform highp sampler2D batchTexture; \n";
|
||||
batchTableShader += `${getGlslComputeSt(this)}\n`;
|
||||
|
||||
const length = attributes.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
batchTableShader += getGlslAttributeFunction(this, i);
|
||||
}
|
||||
|
||||
return function (source) {
|
||||
const mainIndex = source.indexOf("void main");
|
||||
const beforeMain = source.substring(0, mainIndex);
|
||||
const afterMain = source.substring(mainIndex);
|
||||
return `${beforeMain}\n${batchTableShader}\n${afterMain}`;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 BatchTable#destroy
|
||||
*/
|
||||
BatchTable.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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
* @see BatchTable#isDestroyed
|
||||
*/
|
||||
BatchTable.prototype.destroy = function () {
|
||||
this._texture = this._texture && this._texture.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback for updating uniform maps.
|
||||
* @callback BatchTable.updateUniformMapCallback
|
||||
*
|
||||
* @param {object} uniformMap The uniform map.
|
||||
* @returns {object} The new uniform map with properties for retrieving values from the batch table.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A callback for updating a vertex shader source.
|
||||
* @callback BatchTable.updateVertexShaderSourceCallback
|
||||
*
|
||||
* @param {string} vertexShaderSource The vertex shader source.
|
||||
* @returns {string} The new vertex shader source with the functions for retrieving batch table values injected.
|
||||
*/
|
||||
export default BatchTable;
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
import AttributeType from "./AttributeType.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import clone from "../Core/clone.js";
|
||||
import combine from "../Core/combine.js";
|
||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import getBinaryAccessor from "./getBinaryAccessor.js";
|
||||
import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js";
|
||||
|
||||
/**
|
||||
* Object for handling the <code>3DTILES_batch_table_hierarchy</code> extension
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {object} options.extension The <code>3DTILES_batch_table_hierarchy</code> extension object.
|
||||
* @param {Uint8Array} [options.binaryBody] The binary body of the batch table
|
||||
*
|
||||
* @alias BatchTableHierarchy
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function BatchTableHierarchy(options) {
|
||||
this._classes = undefined;
|
||||
this._classIds = undefined;
|
||||
this._classIndexes = undefined;
|
||||
this._parentCounts = undefined;
|
||||
this._parentIndexes = undefined;
|
||||
this._parentIds = undefined;
|
||||
|
||||
// Total memory used by the typed arrays
|
||||
this._byteLength = 0;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options.extension", options.extension);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
initialize(this, options.extension, options.binaryBody);
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
validateHierarchy(this);
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
|
||||
Object.defineProperties(BatchTableHierarchy.prototype, {
|
||||
byteLength: {
|
||||
get: function () {
|
||||
return this._byteLength;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse the batch table hierarchy from the
|
||||
* <code>3DTILES_batch_table_hierarchy</code> extension.
|
||||
*
|
||||
* @param {BatchTableHierarchy} hierarchy The hierarchy instance
|
||||
* @param {object} hierarchyJson The JSON of the extension
|
||||
* @param {Uint8Array} [binaryBody] The binary body of the batch table for accessing binary properties
|
||||
* @private
|
||||
*/
|
||||
function initialize(hierarchy, hierarchyJson, binaryBody) {
|
||||
let i;
|
||||
let classId;
|
||||
let binaryAccessor;
|
||||
|
||||
const instancesLength = hierarchyJson.instancesLength;
|
||||
const classes = hierarchyJson.classes;
|
||||
let classIds = hierarchyJson.classIds;
|
||||
let parentCounts = hierarchyJson.parentCounts;
|
||||
let parentIds = hierarchyJson.parentIds;
|
||||
let parentIdsLength = instancesLength;
|
||||
let byteLength = 0;
|
||||
|
||||
if (defined(classIds.byteOffset)) {
|
||||
classIds.componentType =
|
||||
classIds.componentType ?? ComponentDatatype.UNSIGNED_SHORT;
|
||||
classIds.type = AttributeType.SCALAR;
|
||||
binaryAccessor = getBinaryAccessor(classIds);
|
||||
classIds = binaryAccessor.createArrayBufferView(
|
||||
binaryBody.buffer,
|
||||
binaryBody.byteOffset + classIds.byteOffset,
|
||||
instancesLength,
|
||||
);
|
||||
byteLength += classIds.byteLength;
|
||||
}
|
||||
|
||||
let parentIndexes;
|
||||
if (defined(parentCounts)) {
|
||||
if (defined(parentCounts.byteOffset)) {
|
||||
parentCounts.componentType =
|
||||
parentCounts.componentType ?? ComponentDatatype.UNSIGNED_SHORT;
|
||||
parentCounts.type = AttributeType.SCALAR;
|
||||
binaryAccessor = getBinaryAccessor(parentCounts);
|
||||
parentCounts = binaryAccessor.createArrayBufferView(
|
||||
binaryBody.buffer,
|
||||
binaryBody.byteOffset + parentCounts.byteOffset,
|
||||
instancesLength,
|
||||
);
|
||||
byteLength += parentCounts.byteLength;
|
||||
}
|
||||
parentIndexes = new Uint16Array(instancesLength);
|
||||
parentIdsLength = 0;
|
||||
for (i = 0; i < instancesLength; ++i) {
|
||||
parentIndexes[i] = parentIdsLength;
|
||||
parentIdsLength += parentCounts[i];
|
||||
}
|
||||
|
||||
byteLength += parentIndexes.byteLength;
|
||||
}
|
||||
|
||||
if (defined(parentIds) && defined(parentIds.byteOffset)) {
|
||||
parentIds.componentType =
|
||||
parentIds.componentType ?? ComponentDatatype.UNSIGNED_SHORT;
|
||||
parentIds.type = AttributeType.SCALAR;
|
||||
binaryAccessor = getBinaryAccessor(parentIds);
|
||||
parentIds = binaryAccessor.createArrayBufferView(
|
||||
binaryBody.buffer,
|
||||
binaryBody.byteOffset + parentIds.byteOffset,
|
||||
parentIdsLength,
|
||||
);
|
||||
|
||||
byteLength += parentIds.byteLength;
|
||||
}
|
||||
|
||||
const classesLength = classes.length;
|
||||
for (i = 0; i < classesLength; ++i) {
|
||||
const classInstancesLength = classes[i].length;
|
||||
const properties = classes[i].instances;
|
||||
const binaryProperties = Cesium3DTileBatchTable.getBinaryProperties(
|
||||
classInstancesLength,
|
||||
properties,
|
||||
binaryBody,
|
||||
);
|
||||
byteLength += countBinaryPropertyMemory(binaryProperties);
|
||||
classes[i].instances = combine(binaryProperties, properties);
|
||||
}
|
||||
|
||||
const classCounts = new Array(classesLength).fill(0);
|
||||
const classIndexes = new Uint16Array(instancesLength);
|
||||
for (i = 0; i < instancesLength; ++i) {
|
||||
classId = classIds[i];
|
||||
classIndexes[i] = classCounts[classId];
|
||||
++classCounts[classId];
|
||||
}
|
||||
byteLength += classIndexes.byteLength;
|
||||
|
||||
hierarchy._classes = classes;
|
||||
hierarchy._classIds = classIds;
|
||||
hierarchy._classIndexes = classIndexes;
|
||||
hierarchy._parentCounts = parentCounts;
|
||||
hierarchy._parentIndexes = parentIndexes;
|
||||
hierarchy._parentIds = parentIds;
|
||||
hierarchy._byteLength = byteLength;
|
||||
}
|
||||
|
||||
function countBinaryPropertyMemory(binaryProperties) {
|
||||
let byteLength = 0;
|
||||
for (const name in binaryProperties) {
|
||||
if (binaryProperties.hasOwnProperty(name)) {
|
||||
byteLength += binaryProperties[name].typedArray.byteLength;
|
||||
}
|
||||
}
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
const scratchValidateStack = [];
|
||||
function validateHierarchy(hierarchy) {
|
||||
const stack = scratchValidateStack;
|
||||
stack.length = 0;
|
||||
|
||||
const classIds = hierarchy._classIds;
|
||||
const instancesLength = classIds.length;
|
||||
|
||||
for (let i = 0; i < instancesLength; ++i) {
|
||||
validateInstance(hierarchy, i, stack);
|
||||
}
|
||||
}
|
||||
|
||||
function validateInstance(hierarchy, instanceIndex, stack) {
|
||||
const parentCounts = hierarchy._parentCounts;
|
||||
const parentIds = hierarchy._parentIds;
|
||||
const parentIndexes = hierarchy._parentIndexes;
|
||||
const classIds = hierarchy._classIds;
|
||||
const instancesLength = classIds.length;
|
||||
|
||||
if (!defined(parentIds)) {
|
||||
// No need to validate if there are no parents
|
||||
return;
|
||||
}
|
||||
|
||||
if (instanceIndex >= instancesLength) {
|
||||
throw new DeveloperError(
|
||||
`Parent index ${instanceIndex} exceeds the total number of instances: ${instancesLength}`,
|
||||
);
|
||||
}
|
||||
if (stack.indexOf(instanceIndex) > -1) {
|
||||
throw new DeveloperError(
|
||||
"Circular dependency detected in the batch table hierarchy.",
|
||||
);
|
||||
}
|
||||
|
||||
stack.push(instanceIndex);
|
||||
const parentCount = defined(parentCounts) ? parentCounts[instanceIndex] : 1;
|
||||
const parentIndex = defined(parentCounts)
|
||||
? parentIndexes[instanceIndex]
|
||||
: instanceIndex;
|
||||
for (let i = 0; i < parentCount; ++i) {
|
||||
const parentId = parentIds[parentIndex + i];
|
||||
// Stop the traversal when the instance has no parent (its parentId equals itself), else continue the traversal.
|
||||
if (parentId !== instanceIndex) {
|
||||
validateInstance(hierarchy, parentId, stack);
|
||||
}
|
||||
}
|
||||
stack.pop(instanceIndex);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
// The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large.
|
||||
const scratchVisited = [];
|
||||
const scratchStack = [];
|
||||
let marker = 0;
|
||||
function traverseHierarchyMultipleParents(
|
||||
hierarchy,
|
||||
instanceIndex,
|
||||
endConditionCallback,
|
||||
) {
|
||||
const classIds = hierarchy._classIds;
|
||||
const parentCounts = hierarchy._parentCounts;
|
||||
const parentIds = hierarchy._parentIds;
|
||||
const parentIndexes = hierarchy._parentIndexes;
|
||||
const instancesLength = classIds.length;
|
||||
|
||||
// Ignore instances that have already been visited. This occurs in diamond inheritance situations.
|
||||
// Use a marker value to indicate that an instance has been visited, which increments with each run.
|
||||
// This is more efficient than clearing the visited array every time.
|
||||
const visited = scratchVisited;
|
||||
visited.length = Math.max(visited.length, instancesLength);
|
||||
const visitedMarker = ++marker;
|
||||
|
||||
const stack = scratchStack;
|
||||
stack.length = 0;
|
||||
stack.push(instanceIndex);
|
||||
|
||||
while (stack.length > 0) {
|
||||
instanceIndex = stack.pop();
|
||||
if (visited[instanceIndex] === visitedMarker) {
|
||||
// This instance has already been visited, stop traversal
|
||||
continue;
|
||||
}
|
||||
visited[instanceIndex] = visitedMarker;
|
||||
const result = endConditionCallback(hierarchy, instanceIndex);
|
||||
if (defined(result)) {
|
||||
// The end condition was met, stop the traversal and return the result
|
||||
return result;
|
||||
}
|
||||
const parentCount = parentCounts[instanceIndex];
|
||||
const parentIndex = parentIndexes[instanceIndex];
|
||||
for (let i = 0; i < parentCount; ++i) {
|
||||
const parentId = parentIds[parentIndex + i];
|
||||
// Stop the traversal when the instance has no parent (its parentId equals itself)
|
||||
// else add the parent to the stack to continue the traversal.
|
||||
if (parentId !== instanceIndex) {
|
||||
stack.push(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function traverseHierarchySingleParent(
|
||||
hierarchy,
|
||||
instanceIndex,
|
||||
endConditionCallback,
|
||||
) {
|
||||
let hasParent = true;
|
||||
while (hasParent) {
|
||||
const result = endConditionCallback(hierarchy, instanceIndex);
|
||||
if (defined(result)) {
|
||||
// The end condition was met, stop the traversal and return the result
|
||||
return result;
|
||||
}
|
||||
const parentId = hierarchy._parentIds[instanceIndex];
|
||||
hasParent = parentId !== instanceIndex;
|
||||
instanceIndex = parentId;
|
||||
}
|
||||
}
|
||||
|
||||
function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) {
|
||||
// Traverse over the hierarchy and process each instance with the endConditionCallback.
|
||||
// When the endConditionCallback returns a value, the traversal stops and that value is returned.
|
||||
const parentCounts = hierarchy._parentCounts;
|
||||
const parentIds = hierarchy._parentIds;
|
||||
if (!defined(parentIds)) {
|
||||
return endConditionCallback(hierarchy, instanceIndex);
|
||||
} else if (defined(parentCounts)) {
|
||||
return traverseHierarchyMultipleParents(
|
||||
hierarchy,
|
||||
instanceIndex,
|
||||
endConditionCallback,
|
||||
);
|
||||
}
|
||||
return traverseHierarchySingleParent(
|
||||
hierarchy,
|
||||
instanceIndex,
|
||||
endConditionCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature has this property.
|
||||
*
|
||||
* @param {number} batchId the batch ID of the feature
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @returns {boolean} Whether the feature has this property.
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.hasProperty = function (batchId, propertyId) {
|
||||
const result = traverseHierarchy(
|
||||
this,
|
||||
batchId,
|
||||
function (hierarchy, instanceIndex) {
|
||||
const classId = hierarchy._classIds[instanceIndex];
|
||||
const instances = hierarchy._classes[classId].instances;
|
||||
if (defined(instances[propertyId])) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
return defined(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether any feature has this property.
|
||||
*
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @returns {boolean} Whether any feature has this property.
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.propertyExists = function (propertyId) {
|
||||
const classes = this._classes;
|
||||
const classesLength = classes.length;
|
||||
for (let i = 0; i < classesLength; ++i) {
|
||||
const instances = classes[i].instances;
|
||||
if (defined(instances[propertyId])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of property IDs.
|
||||
*
|
||||
* @param {number} batchId the batch ID of the feature
|
||||
* @param {number} index The index of the entity.
|
||||
* @param {string[]} [results] An array into which to store the results.
|
||||
* @returns {string[]} The property IDs.
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.getPropertyIds = function (batchId, results) {
|
||||
results = defined(results) ? results : [];
|
||||
results.length = 0;
|
||||
|
||||
traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
|
||||
const classId = hierarchy._classIds[instanceIndex];
|
||||
const instances = hierarchy._classes[classId].instances;
|
||||
for (const name in instances) {
|
||||
if (instances.hasOwnProperty(name)) {
|
||||
if (results.indexOf(name) === -1) {
|
||||
results.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the property with the given ID.
|
||||
*
|
||||
* @param {number} batchId the batch ID of the feature
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.getProperty = function (batchId, propertyId) {
|
||||
return traverseHierarchy(this, batchId, function (hierarchy, instanceIndex) {
|
||||
const classId = hierarchy._classIds[instanceIndex];
|
||||
const instanceClass = hierarchy._classes[classId];
|
||||
const indexInClass = hierarchy._classIndexes[instanceIndex];
|
||||
const propertyValues = instanceClass.instances[propertyId];
|
||||
if (defined(propertyValues)) {
|
||||
if (defined(propertyValues.typedArray)) {
|
||||
return getBinaryProperty(propertyValues, indexInClass);
|
||||
}
|
||||
return clone(propertyValues[indexInClass], true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getBinaryProperty(binaryProperty, index) {
|
||||
const typedArray = binaryProperty.typedArray;
|
||||
const componentCount = binaryProperty.componentCount;
|
||||
if (componentCount === 1) {
|
||||
return typedArray[index];
|
||||
}
|
||||
return binaryProperty.type.unpack(typedArray, index * componentCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the property with the given ID. Only properties of the
|
||||
* instance may be set; parent properties may not be set.
|
||||
*
|
||||
* @param {number} batchId The batchId of the feature
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
* @returns {boolean} <code>true</code> if the property was set, <code>false</code> otherwise.
|
||||
*
|
||||
* @exception {DeveloperError} when setting an inherited property
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.setProperty = function (
|
||||
batchId,
|
||||
propertyId,
|
||||
value,
|
||||
) {
|
||||
const result = traverseHierarchy(
|
||||
this,
|
||||
batchId,
|
||||
function (hierarchy, instanceIndex) {
|
||||
const classId = hierarchy._classIds[instanceIndex];
|
||||
const instanceClass = hierarchy._classes[classId];
|
||||
const indexInClass = hierarchy._classIndexes[instanceIndex];
|
||||
const propertyValues = instanceClass.instances[propertyId];
|
||||
if (defined(propertyValues)) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (instanceIndex !== batchId) {
|
||||
throw new DeveloperError(
|
||||
`Inherited property "${propertyId}" is read-only.`,
|
||||
);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
if (defined(propertyValues.typedArray)) {
|
||||
setBinaryProperty(propertyValues, indexInClass, value);
|
||||
} else {
|
||||
propertyValues[indexInClass] = clone(value, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
return defined(result);
|
||||
};
|
||||
|
||||
function setBinaryProperty(binaryProperty, index, value) {
|
||||
const typedArray = binaryProperty.typedArray;
|
||||
const componentCount = binaryProperty.componentCount;
|
||||
if (componentCount === 1) {
|
||||
typedArray[index] = value;
|
||||
} else {
|
||||
binaryProperty.type.pack(value, typedArray, index * componentCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a feature belongs to a class with the given name
|
||||
*
|
||||
* @param {number} batchId The batch ID of the feature
|
||||
* @param {string} className The name of the class
|
||||
* @return {boolean} <code>true</code> if the feature belongs to the class given by className, or <code>false</code> otherwise
|
||||
* @private
|
||||
*/
|
||||
BatchTableHierarchy.prototype.isClass = function (batchId, className) {
|
||||
// PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot
|
||||
// PERFORMANCE_IDEA : treat class names as integers for faster comparisons
|
||||
const result = traverseHierarchy(
|
||||
this,
|
||||
batchId,
|
||||
function (hierarchy, instanceIndex) {
|
||||
const classId = hierarchy._classIds[instanceIndex];
|
||||
const instanceClass = hierarchy._classes[classId];
|
||||
if (instanceClass.name === className) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
return defined(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of the class a given feature belongs to
|
||||
*
|
||||
* @param {number} batchId The batch ID of the feature
|
||||
* @return {string} The name of the class this feature belongs to
|
||||
*/
|
||||
BatchTableHierarchy.prototype.getClassName = function (batchId) {
|
||||
const classId = this._classIds[batchId];
|
||||
const instanceClass = this._classes[classId];
|
||||
return instanceClass.name;
|
||||
};
|
||||
|
||||
export default BatchTableHierarchy;
|
||||
+574
@@ -0,0 +1,574 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import createGuid from "../Core/createGuid.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import PixelFormat from "../Core/PixelFormat.js";
|
||||
import ContextLimits from "../Renderer/ContextLimits.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
import Sampler from "../Renderer/Sampler.js";
|
||||
import Texture from "../Renderer/Texture.js";
|
||||
|
||||
/**
|
||||
* An object that manages color, show/hide and picking textures for a batch
|
||||
* table or feature table.
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {number} featuresLength The number of features in the batch table or feature table
|
||||
* @param {Cesium3DTileContent|ModelFeatureTable} owner The owner of this batch texture. For 3D Tiles, this will be a {@link Cesium3DTileContent}. For glTF models, this will be a {@link ModelFeatureTable}.
|
||||
* @param {object} [statistics] The statistics object to update with information about the batch texture.
|
||||
* @param {Function} [colorChangedCallback] A callback function that is called whenever the color of a feature changes.
|
||||
*
|
||||
* @alias BatchTexture
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function BatchTexture(options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("options.featuresLength", options.featuresLength);
|
||||
Check.typeOf.object("options.owner", options.owner);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._id = createGuid();
|
||||
|
||||
const featuresLength = options.featuresLength;
|
||||
|
||||
// PERFORMANCE_IDEA: These parallel arrays probably generate cache misses in get/set color/show
|
||||
// and use A LOT of memory. How can we use less memory?
|
||||
this._showAlphaProperties = undefined; // [Show (0 or 255), Alpha (0 to 255)] property for each feature
|
||||
this._batchValues = undefined; // Per-feature RGBA (A is based on the color's alpha and feature's show property)
|
||||
|
||||
this._batchValuesDirty = false;
|
||||
this._batchTexture = undefined;
|
||||
this._defaultTexture = undefined;
|
||||
|
||||
this._pickTexture = undefined;
|
||||
this._pickIds = [];
|
||||
|
||||
// Dimensions for batch and pick textures
|
||||
let textureDimensions;
|
||||
let textureStep;
|
||||
|
||||
if (featuresLength > 0) {
|
||||
// PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case
|
||||
// when more than one row is needed (e.g., > 16K features in one tile)
|
||||
const width = Math.min(featuresLength, ContextLimits.maximumTextureSize);
|
||||
const height = Math.ceil(featuresLength / ContextLimits.maximumTextureSize);
|
||||
const stepX = 1.0 / width;
|
||||
const centerX = stepX * 0.5;
|
||||
const stepY = 1.0 / height;
|
||||
const centerY = stepY * 0.5;
|
||||
|
||||
textureDimensions = new Cartesian2(width, height);
|
||||
textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
|
||||
}
|
||||
|
||||
this._translucentFeaturesLength = 0;
|
||||
this._featuresLength = featuresLength;
|
||||
this._textureDimensions = textureDimensions;
|
||||
this._textureStep = textureStep;
|
||||
this._owner = options.owner;
|
||||
this._statistics = options.statistics;
|
||||
this._colorChangedCallback = options.colorChangedCallback;
|
||||
}
|
||||
|
||||
Object.defineProperties(BatchTexture.prototype, {
|
||||
/**
|
||||
* Number of features that are translucent
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
translucentFeaturesLength: {
|
||||
get: function () {
|
||||
return this._translucentFeaturesLength;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Total size of all GPU resources used by this batch texture.
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
byteLength: {
|
||||
get: function () {
|
||||
let memory = 0;
|
||||
if (defined(this._pickTexture)) {
|
||||
memory += this._pickTexture.sizeInBytes;
|
||||
}
|
||||
if (defined(this._batchTexture)) {
|
||||
memory += this._batchTexture.sizeInBytes;
|
||||
}
|
||||
return memory;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Dimensions of the underlying batch texture.
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {Cartesian2}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
textureDimensions: {
|
||||
get: function () {
|
||||
return this._textureDimensions;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Size of each texture and distance from side to center of a texel in
|
||||
* each direction. Stored as (stepX, centerX, stepY, centerY)
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {Cartesian4}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
textureStep: {
|
||||
get: function () {
|
||||
return this._textureStep;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The underlying texture used for styling. The texels are accessed
|
||||
* by batch ID, and the value is the color of this feature after accounting
|
||||
* for show/hide settings.
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
batchTexture: {
|
||||
get: function () {
|
||||
return this._batchTexture;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The default texture to use when there are no batch values
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
defaultTexture: {
|
||||
get: function () {
|
||||
return this._defaultTexture;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The underlying texture used for picking. The texels are accessed by
|
||||
* batch ID, and the value is the pick color.
|
||||
*
|
||||
* @memberof BatchTexture.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
pickTexture: {
|
||||
get: function () {
|
||||
return this._pickTexture;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
BatchTexture.DEFAULT_COLOR_VALUE = Color.WHITE;
|
||||
BatchTexture.DEFAULT_SHOW_VALUE = true;
|
||||
|
||||
function getByteLength(batchTexture) {
|
||||
const dimensions = batchTexture._textureDimensions;
|
||||
return dimensions.x * dimensions.y * 4;
|
||||
}
|
||||
|
||||
function getBatchValues(batchTexture) {
|
||||
if (!defined(batchTexture._batchValues)) {
|
||||
// Default batch texture to RGBA = 255: white highlight (RGB) and show/alpha = true/255 (A).
|
||||
const byteLength = getByteLength(batchTexture);
|
||||
const bytes = new Uint8Array(byteLength).fill(255);
|
||||
batchTexture._batchValues = bytes;
|
||||
}
|
||||
|
||||
return batchTexture._batchValues;
|
||||
}
|
||||
|
||||
function getShowAlphaProperties(batchTexture) {
|
||||
if (!defined(batchTexture._showAlphaProperties)) {
|
||||
const byteLength = 2 * batchTexture._featuresLength;
|
||||
const bytes = new Uint8Array(byteLength).fill(255);
|
||||
// [Show = true, Alpha = 255]
|
||||
batchTexture._showAlphaProperties = bytes;
|
||||
}
|
||||
return batchTexture._showAlphaProperties;
|
||||
}
|
||||
|
||||
function checkBatchId(batchId, featuresLength) {
|
||||
if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
|
||||
throw new DeveloperError(
|
||||
`batchId is required and between zero and featuresLength - 1 (${featuresLength}` -
|
||||
+").",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether a feature is visible.
|
||||
*
|
||||
* @param {number} batchId the ID of the feature
|
||||
* @param {boolean} show <code>true</code> if the feature should be shown, <code>false</code> otherwise
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.setShow = function (batchId, show) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
checkBatchId(batchId, this._featuresLength);
|
||||
Check.typeOf.bool("show", show);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (show && !defined(this._showAlphaProperties)) {
|
||||
// Avoid allocating since the default is show = true
|
||||
return;
|
||||
}
|
||||
|
||||
const showAlphaProperties = getShowAlphaProperties(this);
|
||||
const propertyOffset = batchId * 2;
|
||||
|
||||
const newShow = show ? 255 : 0;
|
||||
if (showAlphaProperties[propertyOffset] !== newShow) {
|
||||
showAlphaProperties[propertyOffset] = newShow;
|
||||
|
||||
const batchValues = getBatchValues(this);
|
||||
|
||||
// Compute alpha used in the shader based on show and color.alpha properties
|
||||
const offset = batchId * 4 + 3;
|
||||
batchValues[offset] = show ? showAlphaProperties[propertyOffset + 1] : 0;
|
||||
|
||||
this._batchValuesDirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the show for all features at once.
|
||||
*
|
||||
* @param {boolean} show <code>true</code> if the feature should be shown, <code>false</code> otherwise
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.setAllShow = function (show) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.bool("show", show);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const featuresLength = this._featuresLength;
|
||||
for (let i = 0; i < featuresLength; ++i) {
|
||||
this.setShow(i, show);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the current show value for a feature
|
||||
*
|
||||
* @param {number} batchId the ID of the feature
|
||||
* @return {boolean} <code>true</code> if the feature is shown, or <code>false</code> otherwise
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.getShow = function (batchId) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
checkBatchId(batchId, this._featuresLength);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(this._showAlphaProperties)) {
|
||||
// Avoid allocating since the default is show = true
|
||||
return true;
|
||||
}
|
||||
|
||||
const offset = batchId * 2;
|
||||
return this._showAlphaProperties[offset] === 255;
|
||||
};
|
||||
|
||||
const scratchColorBytes = new Array(4);
|
||||
|
||||
/**
|
||||
* Set the styling color of a feature
|
||||
*
|
||||
* @param {number} batchId the ID of the feature
|
||||
* @param {Color} color the color to assign to this feature.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.setColor = function (batchId, color) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
checkBatchId(batchId, this._featuresLength);
|
||||
Check.typeOf.object("color", color);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (
|
||||
Color.equals(color, BatchTexture.DEFAULT_COLOR_VALUE) &&
|
||||
!defined(this._batchValues)
|
||||
) {
|
||||
// Avoid allocating since the default is white
|
||||
return;
|
||||
}
|
||||
|
||||
const newColor = color.toBytes(scratchColorBytes);
|
||||
const newAlpha = newColor[3];
|
||||
|
||||
const batchValues = getBatchValues(this);
|
||||
const offset = batchId * 4;
|
||||
|
||||
const showAlphaProperties = getShowAlphaProperties(this);
|
||||
const propertyOffset = batchId * 2;
|
||||
|
||||
if (
|
||||
batchValues[offset] !== newColor[0] ||
|
||||
batchValues[offset + 1] !== newColor[1] ||
|
||||
batchValues[offset + 2] !== newColor[2] ||
|
||||
showAlphaProperties[propertyOffset + 1] !== newAlpha
|
||||
) {
|
||||
batchValues[offset] = newColor[0];
|
||||
batchValues[offset + 1] = newColor[1];
|
||||
batchValues[offset + 2] = newColor[2];
|
||||
|
||||
const wasTranslucent = showAlphaProperties[propertyOffset + 1] !== 255;
|
||||
|
||||
// Compute alpha used in the shader based on show and color.alpha properties
|
||||
const show = showAlphaProperties[propertyOffset] !== 0;
|
||||
batchValues[offset + 3] = show ? newAlpha : 0;
|
||||
showAlphaProperties[propertyOffset + 1] = newAlpha;
|
||||
|
||||
// Track number of translucent features so we know if this tile needs
|
||||
// opaque commands, translucent commands, or both for rendering.
|
||||
const isTranslucent = newAlpha !== 255;
|
||||
if (isTranslucent && !wasTranslucent) {
|
||||
++this._translucentFeaturesLength;
|
||||
} else if (!isTranslucent && wasTranslucent) {
|
||||
--this._translucentFeaturesLength;
|
||||
}
|
||||
|
||||
this._batchValuesDirty = true;
|
||||
|
||||
if (defined(this._colorChangedCallback)) {
|
||||
this._colorChangedCallback(batchId, color);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the styling color for all features at once
|
||||
*
|
||||
* @param {Color} color the color to assign to all features.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.setAllColor = function (color) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("color", color);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const featuresLength = this._featuresLength;
|
||||
for (let i = 0; i < featuresLength; ++i) {
|
||||
this.setColor(i, color);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current color of a feature
|
||||
*
|
||||
* @param {number} batchId The ID of the feature
|
||||
* @param {Color} result A color object where the result will be stored.
|
||||
* @return {Color} The color assigned to the selected feature
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.getColor = function (batchId, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
checkBatchId(batchId, this._featuresLength);
|
||||
Check.typeOf.object("result", result);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(this._batchValues)) {
|
||||
return Color.clone(BatchTexture.DEFAULT_COLOR_VALUE, result);
|
||||
}
|
||||
|
||||
const batchValues = this._batchValues;
|
||||
const offset = batchId * 4;
|
||||
|
||||
const showAlphaProperties = this._showAlphaProperties;
|
||||
const propertyOffset = batchId * 2;
|
||||
|
||||
return Color.fromBytes(
|
||||
batchValues[offset],
|
||||
batchValues[offset + 1],
|
||||
batchValues[offset + 2],
|
||||
showAlphaProperties[propertyOffset + 1],
|
||||
result,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the pick color of a feature. This feature is an RGBA encoding of the
|
||||
* pick ID.
|
||||
*
|
||||
* @param {number} batchId The ID of the feature
|
||||
* @return {PickId} The picking color assigned to this feature
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.getPickColor = function (batchId) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
checkBatchId(batchId, this._featuresLength);
|
||||
//>>includeEnd('debug');
|
||||
return this._pickIds[batchId];
|
||||
};
|
||||
|
||||
function createTexture(batchTexture, context, bytes) {
|
||||
const dimensions = batchTexture._textureDimensions;
|
||||
return new Texture({
|
||||
context: context,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
||||
source: {
|
||||
width: dimensions.x,
|
||||
height: dimensions.y,
|
||||
arrayBufferView: bytes,
|
||||
},
|
||||
flipY: false,
|
||||
sampler: Sampler.NEAREST,
|
||||
});
|
||||
}
|
||||
|
||||
function createPickTexture(batchTexture, context) {
|
||||
const featuresLength = batchTexture._featuresLength;
|
||||
if (!defined(batchTexture._pickTexture) && featuresLength > 0) {
|
||||
const pickIds = batchTexture._pickIds;
|
||||
const byteLength = getByteLength(batchTexture);
|
||||
const bytes = new Uint8Array(byteLength);
|
||||
const owner = batchTexture._owner;
|
||||
const statistics = batchTexture._statistics;
|
||||
|
||||
// PERFORMANCE_IDEA: we could skip the pick texture completely by allocating
|
||||
// a continuous range of pickIds and then converting the base pickId + batchId
|
||||
// to RGBA in the shader. The only consider is precision issues, which might
|
||||
// not be an issue in WebGL 2.
|
||||
for (let i = 0; i < featuresLength; ++i) {
|
||||
const pickId = context.createPickId(owner.getFeature(i));
|
||||
pickIds.push(pickId);
|
||||
|
||||
const pickColor = pickId.color;
|
||||
const offset = i * 4;
|
||||
bytes[offset] = Color.floatToByte(pickColor.red);
|
||||
bytes[offset + 1] = Color.floatToByte(pickColor.green);
|
||||
bytes[offset + 2] = Color.floatToByte(pickColor.blue);
|
||||
bytes[offset + 3] = Color.floatToByte(pickColor.alpha);
|
||||
}
|
||||
|
||||
batchTexture._pickTexture = createTexture(batchTexture, context, bytes);
|
||||
|
||||
// Make sure the tileset statistics are updated the frame when the pick
|
||||
// texture is created.
|
||||
if (defined(statistics)) {
|
||||
statistics.batchTableByteLength += batchTexture._pickTexture.sizeInBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateBatchTexture(batchTexture) {
|
||||
const dimensions = batchTexture._textureDimensions;
|
||||
// PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained
|
||||
// texture updates when less than, for example, 10%, of the values changed. Or
|
||||
// even just optimize the common case when one feature show/color changed.
|
||||
batchTexture._batchTexture.copyFrom({
|
||||
source: {
|
||||
width: dimensions.x,
|
||||
height: dimensions.y,
|
||||
arrayBufferView: batchTexture._batchValues,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
BatchTexture.prototype.update = function (tileset, frameState) {
|
||||
const context = frameState.context;
|
||||
this._defaultTexture = context.defaultTexture;
|
||||
|
||||
const passes = frameState.passes;
|
||||
if (passes.pick || passes.postProcess) {
|
||||
createPickTexture(this, context);
|
||||
}
|
||||
|
||||
if (this._batchValuesDirty) {
|
||||
this._batchValuesDirty = false;
|
||||
|
||||
// Create batch texture on-demand
|
||||
if (!defined(this._batchTexture)) {
|
||||
this._batchTexture = createTexture(this, context, this._batchValues);
|
||||
|
||||
// Make sure the tileset statistics are updated the frame when the
|
||||
// batch texture is created.
|
||||
if (defined(this._statistics)) {
|
||||
this._statistics.batchTableByteLength += this._batchTexture.sizeInBytes;
|
||||
}
|
||||
}
|
||||
|
||||
updateBatchTexture(this); // Apply per-feature show/color updates
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 BatchTexture#destroy
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.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 BatchTexture#isDestroyed
|
||||
* @private
|
||||
*/
|
||||
BatchTexture.prototype.destroy = function () {
|
||||
this._batchTexture = this._batchTexture && this._batchTexture.destroy();
|
||||
this._pickTexture = this._pickTexture && this._pickTexture.destroy();
|
||||
|
||||
const pickIds = this._pickIds;
|
||||
const length = pickIds.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
pickIds[i].destroy();
|
||||
}
|
||||
|
||||
return destroyObject(this);
|
||||
};
|
||||
|
||||
export default BatchTexture;
|
||||
+1604
File diff suppressed because it is too large
Load Diff
+2150
File diff suppressed because it is too large
Load Diff
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Defines loading state of a image as any request is resolved and the WebGL resources are updated.
|
||||
* @private
|
||||
* @enum {number}
|
||||
*/
|
||||
const BillboardLoadState = Object.freeze({
|
||||
/**
|
||||
* There is no image data to load.
|
||||
* @private
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
NONE: 0,
|
||||
/**
|
||||
* Image data is in the process of downloading, or WebGL resources are being updated.
|
||||
* @private
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
LOADING: 2,
|
||||
/**
|
||||
* The image data has been downloaded and the WebGL resources have been created. It is ready for rendering.
|
||||
* @private
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
LOADED: 3,
|
||||
/**
|
||||
* There was an error while downloading an image or updating the WebGL resources.
|
||||
* @private
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ERROR: 4,
|
||||
/**
|
||||
* Updating the WebGL resources failed, due to the resource being destroyed or another error.
|
||||
* @private
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
FAILED: 5,
|
||||
});
|
||||
|
||||
export default BillboardLoadState;
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import BillboardLoadState from "./BillboardLoadState.js";
|
||||
|
||||
/**
|
||||
* Tracks a reference to an image and it's loading state, as used in a BillboardCollection and stored in a texture atlas.
|
||||
* @constructor
|
||||
* @private
|
||||
* @see BillboardCollection
|
||||
* @see Billboard#image
|
||||
* @alias BillboardTexture
|
||||
* @param {BillboardCollection} billboardCollection The associated billboard collecion.
|
||||
*/
|
||||
function BillboardTexture(billboardCollection) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("billboardCollection", billboardCollection);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._billboardCollection = billboardCollection;
|
||||
|
||||
this._id = undefined;
|
||||
this._loadState = BillboardLoadState.NONE;
|
||||
this._loadError = undefined;
|
||||
|
||||
this._index = -1;
|
||||
this._width = undefined;
|
||||
this._height = undefined;
|
||||
|
||||
this._hasSubregion = false;
|
||||
|
||||
/**
|
||||
* Used by billboardCollection to track whcih billboards to update.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
Object.defineProperties(BillboardTexture.prototype, {
|
||||
/**
|
||||
* If defined, this error was encountered during the loading process.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {Error|undefined}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
loadError: {
|
||||
get: function () {
|
||||
return this._loadError;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The current status of the image load. When <code>BillboardLoadState.LOADED</code>, this billboard is ready to render, i.e., the image
|
||||
* has been downloaded and the WebGL resources are created.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {BillboardLoadState}
|
||||
* @readonly
|
||||
* @default BillboardLoadState.NONE
|
||||
* @private
|
||||
*/
|
||||
loadState: {
|
||||
get: function () {
|
||||
return this._loadState;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* When <code>true</code>, this texture is ready to render, i.e., the image
|
||||
* has been downloaded and the WebGL resources are created.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default false
|
||||
* @private
|
||||
*/
|
||||
ready: {
|
||||
get: function () {
|
||||
return this._loadState === BillboardLoadState.LOADED;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if there is image data associated with this instance.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
hasImage: {
|
||||
get: function () {
|
||||
return this._loadState !== BillboardLoadState.NONE;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A unique identifier for the image, or undefined if no image data has been associated with this instance.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {string|undefined}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
id: {
|
||||
get: function () {
|
||||
return this._id;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The width of the associated image. Before the instance is <code>ready</code>, this will be <code>undefined</code>.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
width: {
|
||||
get: function () {
|
||||
return this._width;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The height of the associated image. Before the instance is <code>ready</code>, this will be <code>undefined</code>.
|
||||
* @memberof BillboardTexture.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
height: {
|
||||
get: function () {
|
||||
return this._height;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Releases reference to any associated image data.
|
||||
* @private
|
||||
*/
|
||||
BillboardTexture.prototype.unload = async function () {
|
||||
if (this._loadState === BillboardLoadState.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._id = undefined;
|
||||
this._loadError = undefined;
|
||||
this._loadState = BillboardLoadState.NONE;
|
||||
|
||||
this._index = -1;
|
||||
this._width = undefined;
|
||||
this._height = undefined;
|
||||
|
||||
this.dirty = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts loading an image into the texture atlas.
|
||||
* @see {TextureAtlas#addImage}
|
||||
* @private
|
||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||
* @param {HTMLImageElement|HTMLCanvasElement|string|Resource|Promise|TextureAtlas.CreateImageCallback} image An image or canvas to add to the texture atlas,
|
||||
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
|
||||
* @param {number} width A number specifying the width of the texture. If undefined, the image width will be used.
|
||||
* @param {number} height A number specifying the height of the texture. If undefined, the image height will be used.
|
||||
*/
|
||||
BillboardTexture.prototype.loadImage = async function (
|
||||
id,
|
||||
image,
|
||||
width,
|
||||
height,
|
||||
) {
|
||||
if (this._id === id) {
|
||||
// This image has already been loaded
|
||||
return;
|
||||
}
|
||||
|
||||
const collection = this._billboardCollection;
|
||||
const cache = collection.billboardTextureCache;
|
||||
let billboardTexture = cache.get(id);
|
||||
if (
|
||||
(defined(billboardTexture) &&
|
||||
image.loadState === BillboardLoadState.LOADING) ||
|
||||
image.loadState === BillboardLoadState.LOADED
|
||||
) {
|
||||
// Use the cached texture if it is in progress or successful.
|
||||
BillboardTexture.clone(billboardTexture, this);
|
||||
return;
|
||||
}
|
||||
// Otherwise, load if not yet assigned an image, and try the load again if anything failed during the last billboard creation
|
||||
if (!defined(billboardTexture)) {
|
||||
billboardTexture = new BillboardTexture(collection);
|
||||
cache.set(id, billboardTexture);
|
||||
}
|
||||
|
||||
billboardTexture._id = this._id = id;
|
||||
billboardTexture._loadState = this._loadState = BillboardLoadState.LOADING;
|
||||
billboardTexture._loadError = this._loadError = undefined;
|
||||
|
||||
let index;
|
||||
const atlas = this._billboardCollection.textureAtlas;
|
||||
try {
|
||||
index = atlas.addImage(id, image, width, height);
|
||||
if (index instanceof Promise) {
|
||||
index = await index;
|
||||
}
|
||||
} catch (error) {
|
||||
// There was an error loading the image
|
||||
billboardTexture._loadState = BillboardLoadState.ERROR;
|
||||
billboardTexture._loadError = error;
|
||||
|
||||
if (this._id !== id) {
|
||||
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadState = BillboardLoadState.ERROR;
|
||||
this._loadError = error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined(index) || index === -1) {
|
||||
// Resources destroyed or otherwise
|
||||
billboardTexture._loadState = BillboardLoadState.FAILED;
|
||||
billboardTexture._index = -1;
|
||||
|
||||
if (this._id !== id) {
|
||||
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadState = BillboardLoadState.FAILED;
|
||||
this._index = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
billboardTexture._index = index;
|
||||
billboardTexture._loadState = BillboardLoadState.LOADED;
|
||||
|
||||
const rectangle = atlas.rectangles[index];
|
||||
billboardTexture._width = rectangle.width;
|
||||
billboardTexture._height = rectangle.height;
|
||||
|
||||
if (this._id !== id) {
|
||||
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
this._index = index;
|
||||
this._loadState = BillboardLoadState.LOADED;
|
||||
this._width = rectangle.width;
|
||||
this._height = rectangle.height;
|
||||
|
||||
this.dirty = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Track a reference to a sub-region of an existing image.
|
||||
* @see {TextureAtlas#addImageSubRegion}
|
||||
* @private
|
||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||
*/
|
||||
BillboardTexture.prototype.addImageSubRegion = function (id, subRegion) {
|
||||
this._id = id;
|
||||
this._loadError = undefined;
|
||||
this._hasSubregion = true;
|
||||
|
||||
const atlas = this._billboardCollection.textureAtlas;
|
||||
const indexOrPromise = atlas.addImageSubRegion(id, subRegion);
|
||||
|
||||
if (typeof indexOrPromise === "number") {
|
||||
this.setImageSubRegion(indexOrPromise, subRegion);
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadImageSubRegion(id, subRegion, indexOrPromise);
|
||||
};
|
||||
|
||||
/**
|
||||
* @see {TextureAtlas#addImageSubRegion}
|
||||
* @private
|
||||
* @param {string} id An identifier to detect whether the image already exists in the atlas.
|
||||
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||
* @param {Promise<number>} indexPromise A promise that resolves to the image region index.
|
||||
*/
|
||||
BillboardTexture.prototype.loadImageSubRegion = async function (
|
||||
id,
|
||||
subRegion,
|
||||
indexPromise,
|
||||
) {
|
||||
let index;
|
||||
try {
|
||||
this._loadState = BillboardLoadState.LOADING;
|
||||
index = await indexPromise;
|
||||
} catch (error) {
|
||||
// There was an error loading the referenced image
|
||||
this._loadState = BillboardLoadState.ERROR;
|
||||
this._loadError = error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._id !== id) {
|
||||
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadState = BillboardLoadState.LOADED;
|
||||
|
||||
this.setImageSubRegion(index, subRegion);
|
||||
};
|
||||
|
||||
/**
|
||||
* @see {TextureAtlas#addImageSubRegion}
|
||||
* @private
|
||||
* @param {number} index The resolved index in the {@link TextureAtlas}
|
||||
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} defining a region of an existing image, measured in pixels from the bottom-left of the image.
|
||||
*/
|
||||
BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
|
||||
if (this._index === index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined(index) || index === -1) {
|
||||
this._loadState = BillboardLoadState.FAILED;
|
||||
this._index = -1;
|
||||
this._width = undefined;
|
||||
this._height = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this._width = subRegion.width;
|
||||
this._height = subRegion.height;
|
||||
|
||||
this._index = index;
|
||||
|
||||
this.dirty = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the texture coordinates for reading the loaded texture in shaders.
|
||||
* @private
|
||||
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
|
||||
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
|
||||
*/
|
||||
BillboardTexture.prototype.computeTextureCoordinates = function (result) {
|
||||
const atlas = this._billboardCollection.textureAtlas;
|
||||
return atlas.computeTextureCoordinates(this._index, result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones an existing billboard texture, inlcuding any in-flight tracking, into the target billboard texture.
|
||||
* @param {BillboardTexture} billboardTexture
|
||||
* @param {BillboardTexture} target
|
||||
* @returns {BillboardTexture} target
|
||||
*/
|
||||
BillboardTexture.clone = function (billboardTexture, target) {
|
||||
target._id = billboardTexture._id;
|
||||
target._loadState = billboardTexture._loadState;
|
||||
target._loadError = undefined;
|
||||
target._index = billboardTexture._index;
|
||||
target._width = billboardTexture._width;
|
||||
target._height = billboardTexture._height;
|
||||
target._hasSubregion = billboardTexture._hasSubregion;
|
||||
|
||||
if (billboardTexture.ready) {
|
||||
target.dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const completeLoad = async () => {
|
||||
const id = billboardTexture._id;
|
||||
const atlas = billboardTexture._billboardCollection.textureAtlas;
|
||||
await atlas._indexPromiseById.get(id);
|
||||
|
||||
// Any errors should have already been handled
|
||||
if (target._id !== id) {
|
||||
// Another load was initiated and resolved resolved before this one. This operation is cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (billboardTexture._hasSubregion) {
|
||||
// Subregions must wait an additional frame to be ready
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
target._id = id;
|
||||
target._loadState = billboardTexture._loadState;
|
||||
target._loadError = billboardTexture._loadError;
|
||||
target._index = billboardTexture._index;
|
||||
target._width = billboardTexture._width;
|
||||
target._height = billboardTexture._height;
|
||||
target.dirty = true;
|
||||
};
|
||||
|
||||
completeLoad();
|
||||
return target;
|
||||
};
|
||||
|
||||
export default BillboardTexture;
|
||||
+769
@@ -0,0 +1,769 @@
|
||||
import buildModuleUrl from "../Core/buildModuleUrl.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Event from "../Core/Event.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
import TileProviderError from "../Core/TileProviderError.js";
|
||||
import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
|
||||
import BingMapsStyle from "./BingMapsStyle.js";
|
||||
import DiscardEmptyTilePolicy from "./DiscardEmptyTileImagePolicy.js";
|
||||
import ImageryProvider from "./ImageryProvider.js";
|
||||
|
||||
/**
|
||||
* @typedef {object} BingMapsImageryProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the BingMapsImageryProvider constructor
|
||||
*
|
||||
* @property {string} [key] The Bing Maps key for your application, which can be
|
||||
* created at {@link https://www.bingmapsportal.com/}.
|
||||
* @property {string} [tileProtocol] The protocol to use when loading tiles, e.g. 'http' or 'https'.
|
||||
* By default, tiles are loaded using the same protocol as the page.
|
||||
* @property {BingMapsStyle} [mapStyle=BingMapsStyle.AERIAL] The type of Bing Maps imagery to load.
|
||||
* @property {string} [mapLayer] Additional display layer options as defined on {@link https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters}
|
||||
* @property {string} [culture=''] The culture to use when requesting Bing Maps imagery. Not
|
||||
* all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
|
||||
* for information on the supported cultures.
|
||||
* @property {Ellipsoid} [ellipsoid=Ellipsoid.default] The ellipsoid. If not specified, the default ellipsoid is used.
|
||||
* @property {TileDiscardPolicy} [tileDiscardPolicy] The policy that determines if a tile
|
||||
* is invalid and should be discarded. By default, a {@link DiscardEmptyTileImagePolicy}
|
||||
* will be used, with the expectation that the Bing Maps server will send a zero-length response for missing tiles.
|
||||
* To ensure that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this parameter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used to track creation details while fetching initial metadata
|
||||
*
|
||||
* @constructor
|
||||
* @private
|
||||
*
|
||||
* @param {BingMapsImageryProvider.ConstructorOptions} options An object describing initialization options
|
||||
*/
|
||||
function ImageryProviderBuilder(options) {
|
||||
this.tileWidth = undefined;
|
||||
this.tileHeight = undefined;
|
||||
this.maximumLevel = undefined;
|
||||
this.imageUrlSubdomains = undefined;
|
||||
this.imageUrlTemplate = undefined;
|
||||
|
||||
this.attributionList = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete BingMapsImageryProvider creation based on builder values.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {BingMapsImageryProvider} provider
|
||||
*/
|
||||
ImageryProviderBuilder.prototype.build = function (provider) {
|
||||
provider._tileWidth = this.tileWidth;
|
||||
provider._tileHeight = this.tileHeight;
|
||||
provider._maximumLevel = this.maximumLevel;
|
||||
provider._imageUrlSubdomains = this.imageUrlSubdomains;
|
||||
provider._imageUrlTemplate = this.imageUrlTemplate;
|
||||
|
||||
let attributionList = (provider._attributionList = this.attributionList);
|
||||
if (!attributionList) {
|
||||
attributionList = [];
|
||||
}
|
||||
provider._attributionList = attributionList;
|
||||
|
||||
for (
|
||||
let attributionIndex = 0, attributionLength = attributionList.length;
|
||||
attributionIndex < attributionLength;
|
||||
++attributionIndex
|
||||
) {
|
||||
const attribution = attributionList[attributionIndex];
|
||||
|
||||
if (attribution.credit instanceof Credit) {
|
||||
// If attribution.credit has already been created
|
||||
// then we are using a cached value, which means
|
||||
// none of the remaining processing needs to be done.
|
||||
break;
|
||||
}
|
||||
|
||||
attribution.credit = new Credit(attribution.attribution);
|
||||
const coverageAreas = attribution.coverageAreas;
|
||||
|
||||
for (
|
||||
let areaIndex = 0, areaLength = attribution.coverageAreas.length;
|
||||
areaIndex < areaLength;
|
||||
++areaIndex
|
||||
) {
|
||||
const area = coverageAreas[areaIndex];
|
||||
const bbox = area.bbox;
|
||||
area.bbox = new Rectangle(
|
||||
CesiumMath.toRadians(bbox[1]),
|
||||
CesiumMath.toRadians(bbox[0]),
|
||||
CesiumMath.toRadians(bbox[3]),
|
||||
CesiumMath.toRadians(bbox[2]),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function metadataSuccess(data, imageryProviderBuilder) {
|
||||
if (data.resourceSets.length !== 1) {
|
||||
throw new RuntimeError(
|
||||
"metadata does not specify one resource in resourceSets",
|
||||
);
|
||||
}
|
||||
|
||||
const resource = data.resourceSets[0].resources[0];
|
||||
imageryProviderBuilder.tileWidth = resource.imageWidth;
|
||||
imageryProviderBuilder.tileHeight = resource.imageHeight;
|
||||
imageryProviderBuilder.maximumLevel = resource.zoomMax - 1;
|
||||
imageryProviderBuilder.imageUrlSubdomains = resource.imageUrlSubdomains;
|
||||
imageryProviderBuilder.imageUrlTemplate = resource.imageUrl;
|
||||
|
||||
let validProviders = resource.imageryProviders;
|
||||
if (defined(resource.imageryProviders)) {
|
||||
// prevent issues with the imagery API from crashing the viewer when the expected properties are not there
|
||||
// See https://github.com/CesiumGS/cesium/issues/12088
|
||||
validProviders = resource.imageryProviders.filter((provider) =>
|
||||
provider.coverageAreas?.some((area) => defined(area.bbox)),
|
||||
);
|
||||
}
|
||||
imageryProviderBuilder.attributionList = validProviders;
|
||||
}
|
||||
|
||||
function metadataFailure(metadataResource, error, provider) {
|
||||
let message = `An error occurred while accessing ${metadataResource.url}`;
|
||||
if (defined(error) && defined(error.message)) {
|
||||
message += `: ${error.message}`;
|
||||
}
|
||||
|
||||
TileProviderError.reportError(
|
||||
undefined,
|
||||
provider,
|
||||
defined(provider) ? provider._errorEvent : undefined,
|
||||
message,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
error,
|
||||
);
|
||||
|
||||
throw new RuntimeError(message);
|
||||
}
|
||||
|
||||
async function requestMetadata(
|
||||
metadataResource,
|
||||
imageryProviderBuilder,
|
||||
provider,
|
||||
) {
|
||||
const cacheKey = metadataResource.url;
|
||||
let promise = BingMapsImageryProvider._metadataCache[cacheKey];
|
||||
if (!defined(promise)) {
|
||||
promise = metadataResource.fetchJson();
|
||||
BingMapsImageryProvider._metadataCache[cacheKey] = promise;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await promise;
|
||||
return metadataSuccess(data, imageryProviderBuilder);
|
||||
} catch (e) {
|
||||
metadataFailure(metadataResource, e, provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <div class="notice">
|
||||
* To construct a BingMapsImageryProvider, call {@link BingMapsImageryProvider.fromUrl}. Do not call the constructor directly.
|
||||
* </div>
|
||||
*
|
||||
* Provides tiled imagery using the Bing Maps Imagery REST API.
|
||||
*
|
||||
* @alias BingMapsImageryProvider
|
||||
* @constructor
|
||||
*
|
||||
* @param {BingMapsImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||
*
|
||||
* @see BingMapsImageryProvider.fromUrl
|
||||
* @see ArcGisMapServerImageryProvider
|
||||
* @see GoogleEarthEnterpriseMapsProvider
|
||||
* @see OpenStreetMapImageryProvider
|
||||
* @see SingleTileImageryProvider
|
||||
* @see TileMapServiceImageryProvider
|
||||
* @see WebMapServiceImageryProvider
|
||||
* @see WebMapTileServiceImageryProvider
|
||||
* @see UrlTemplateImageryProvider
|
||||
*
|
||||
* @example
|
||||
* const bing = await Cesium.BingMapsImageryProvider.fromUrl(
|
||||
* "https://dev.virtualearth.net", {
|
||||
* key: "get-yours-at-https://www.bingmapsportal.com/",
|
||||
* mapStyle: Cesium.BingMapsStyle.AERIAL
|
||||
* });
|
||||
*
|
||||
* @see {@link http://msdn.microsoft.com/en-us/library/ff701713.aspx|Bing Maps REST Services}
|
||||
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
|
||||
*/
|
||||
function BingMapsImageryProvider(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
this._defaultAlpha = undefined;
|
||||
this._defaultNightAlpha = undefined;
|
||||
this._defaultDayAlpha = undefined;
|
||||
this._defaultBrightness = undefined;
|
||||
this._defaultContrast = undefined;
|
||||
this._defaultHue = undefined;
|
||||
this._defaultSaturation = undefined;
|
||||
this._defaultGamma = 1.0;
|
||||
this._defaultMinificationFilter = undefined;
|
||||
this._defaultMagnificationFilter = undefined;
|
||||
|
||||
this._mapStyle = options.mapStyle ?? BingMapsStyle.AERIAL;
|
||||
this._mapLayer = options.mapLayer;
|
||||
this._culture = options.culture ?? "";
|
||||
this._key = options.key;
|
||||
|
||||
this._tileDiscardPolicy = options.tileDiscardPolicy;
|
||||
if (!defined(this._tileDiscardPolicy)) {
|
||||
this._tileDiscardPolicy = new DiscardEmptyTilePolicy();
|
||||
}
|
||||
|
||||
this._proxy = options.proxy;
|
||||
this._credit = new Credit(
|
||||
`<a href="https://www.microsoft.com/en-us/maps/bing-maps/product"><img src="${BingMapsImageryProvider.logoUrl}" title="Bing Imagery"/></a>`,
|
||||
);
|
||||
|
||||
this._tilingScheme = new WebMercatorTilingScheme({
|
||||
numberOfLevelZeroTilesX: 2,
|
||||
numberOfLevelZeroTilesY: 2,
|
||||
ellipsoid: options.ellipsoid,
|
||||
});
|
||||
|
||||
this._tileWidth = undefined;
|
||||
this._tileHeight = undefined;
|
||||
this._maximumLevel = undefined;
|
||||
this._imageUrlTemplate = undefined;
|
||||
this._imageUrlSubdomains = undefined;
|
||||
this._attributionList = undefined;
|
||||
|
||||
this._errorEvent = new Event();
|
||||
}
|
||||
|
||||
Object.defineProperties(BingMapsImageryProvider.prototype, {
|
||||
/**
|
||||
* Gets the name of the BingMaps server url hosting the imagery.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
url: {
|
||||
get: function () {
|
||||
return this._resource.url;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the proxy used by this provider.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {Proxy}
|
||||
* @readonly
|
||||
*/
|
||||
proxy: {
|
||||
get: function () {
|
||||
return this._resource.proxy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the Bing Maps key.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
key: {
|
||||
get: function () {
|
||||
return this._key;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the type of Bing Maps imagery to load.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {BingMapsStyle}
|
||||
* @readonly
|
||||
*/
|
||||
mapStyle: {
|
||||
get: function () {
|
||||
return this._mapStyle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the additional map layer options as defined in {@link https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters}/
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
mapLayer: {
|
||||
get: function () {
|
||||
return this._mapLayer;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The culture to use when requesting Bing Maps imagery. Not
|
||||
* all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
|
||||
* for information on the supported cultures.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
culture: {
|
||||
get: function () {
|
||||
return this._culture;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the width of each tile, in pixels.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileWidth: {
|
||||
get: function () {
|
||||
return this._tileWidth;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the height of each tile, in pixels.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
tileHeight: {
|
||||
get: function () {
|
||||
return this._tileHeight;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum level-of-detail that can be requested.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumLevel: {
|
||||
get: function () {
|
||||
return this._maximumLevel;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum level-of-detail that can be requested.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
minimumLevel: {
|
||||
get: function () {
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tiling scheme used by this provider.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {TilingScheme}
|
||||
* @readonly
|
||||
*/
|
||||
tilingScheme: {
|
||||
get: function () {
|
||||
return this._tilingScheme;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the rectangle, in radians, of the imagery provided by this instance.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {Rectangle}
|
||||
* @readonly
|
||||
*/
|
||||
rectangle: {
|
||||
get: function () {
|
||||
return this._tilingScheme.rectangle;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tile discard policy. If not undefined, the discard policy is responsible
|
||||
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
|
||||
* returns undefined, no tiles are filtered.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {TileDiscardPolicy}
|
||||
* @readonly
|
||||
*/
|
||||
tileDiscardPolicy: {
|
||||
get: function () {
|
||||
return this._tileDiscardPolicy;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
|
||||
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
|
||||
* are passed an instance of {@link TileProviderError}.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
errorEvent: {
|
||||
get: function () {
|
||||
return this._errorEvent;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
|
||||
* the source of the imagery.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return this._credit;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether or not the images provided by this imagery provider
|
||||
* include an alpha channel. If this property is false, an alpha channel, if present, will
|
||||
* be ignored. If this property is true, any images without an alpha channel will be treated
|
||||
* as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage
|
||||
* and texture upload time.
|
||||
* @memberof BingMapsImageryProvider.prototype
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
hasAlphaChannel: {
|
||||
get: function () {
|
||||
return defined(this.mapLayer);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an {@link ImageryProvider} which provides tiled imagery using the Bing Maps Imagery REST API.
|
||||
*
|
||||
* @param {Resource|string} url The url of the Bing Maps server hosting the imagery.
|
||||
* @param {BingMapsImageryProvider.ConstructorOptions} options Object describing initialization options
|
||||
* @returns {Promise<BingMapsImageryProvider>} A promise that resolves to the created BingMapsImageryProvider
|
||||
*
|
||||
* @example
|
||||
* const bing = await Cesium.BingMapsImageryProvider.fromUrl(
|
||||
* "https://dev.virtualearth.net", {
|
||||
* key: "get-yours-at-https://www.bingmapsportal.com/",
|
||||
* mapStyle: Cesium.BingMapsStyle.AERIAL
|
||||
* });
|
||||
*
|
||||
* @exception {RuntimeError} metadata does not specify one resource in resourceSets
|
||||
*/
|
||||
BingMapsImageryProvider.fromUrl = async function (url, options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("url", url);
|
||||
Check.defined("options.key", options.key);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
let tileProtocol = options.tileProtocol;
|
||||
|
||||
// For backward compatibility reasons, the tileProtocol may end with
|
||||
// a `:`. Remove it.
|
||||
if (defined(tileProtocol)) {
|
||||
if (
|
||||
tileProtocol.length > 0 &&
|
||||
tileProtocol[tileProtocol.length - 1] === ":"
|
||||
) {
|
||||
tileProtocol = tileProtocol.substr(0, tileProtocol.length - 1);
|
||||
}
|
||||
} else {
|
||||
// use http if the document's protocol is http, otherwise use https
|
||||
const documentProtocol = document.location.protocol;
|
||||
tileProtocol = documentProtocol === "http:" ? "http" : "https";
|
||||
}
|
||||
|
||||
const mapStyle = options.mapStyle ?? BingMapsStyle.AERIAL;
|
||||
const resource = Resource.createIfNeeded(url);
|
||||
resource.appendForwardSlash();
|
||||
|
||||
const queryParameters = {
|
||||
incl: "ImageryProviders",
|
||||
key: options.key,
|
||||
uriScheme: tileProtocol,
|
||||
};
|
||||
|
||||
if (defined(options.mapLayer)) {
|
||||
queryParameters.mapLayer = options.mapLayer;
|
||||
}
|
||||
|
||||
if (defined(options.culture)) {
|
||||
queryParameters.culture = options.culture;
|
||||
}
|
||||
|
||||
const metadataResource = resource.getDerivedResource({
|
||||
url: `REST/v1/Imagery/Metadata/${mapStyle}`,
|
||||
queryParameters: queryParameters,
|
||||
});
|
||||
|
||||
const provider = new BingMapsImageryProvider(options);
|
||||
provider._resource = resource;
|
||||
const imageryProviderBuilder = new ImageryProviderBuilder(options);
|
||||
await requestMetadata(metadataResource, imageryProviderBuilder);
|
||||
imageryProviderBuilder.build(provider);
|
||||
return provider;
|
||||
};
|
||||
|
||||
const rectangleScratch = new Rectangle();
|
||||
|
||||
/**
|
||||
* Gets the credits to be displayed when a given tile is displayed.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level;
|
||||
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
|
||||
*/
|
||||
BingMapsImageryProvider.prototype.getTileCredits = function (x, y, level) {
|
||||
const rectangle = this._tilingScheme.tileXYToRectangle(
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
rectangleScratch,
|
||||
);
|
||||
const result = getRectangleAttribution(
|
||||
this._attributionList,
|
||||
level,
|
||||
rectangle,
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests the image for a given tile.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {Request} [request] The request object. Intended for internal use only.
|
||||
* @returns {Promise<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
|
||||
* undefined if there are too many active requests to the server, and the request should be retried later.
|
||||
*/
|
||||
BingMapsImageryProvider.prototype.requestImage = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
request,
|
||||
) {
|
||||
const promise = ImageryProvider.loadImage(
|
||||
this,
|
||||
buildImageResource(this, x, y, level, request),
|
||||
);
|
||||
|
||||
if (defined(promise)) {
|
||||
return promise.catch(function (error) {
|
||||
// One cause of an error here is that the image we tried to load was zero-length.
|
||||
// This isn't actually a problem, since it indicates that there is no tile.
|
||||
// So, in that case we return the EMPTY_IMAGE sentinel value for later discarding.
|
||||
if (defined(error.blob) && error.blob.size === 0) {
|
||||
return DiscardEmptyTilePolicy.EMPTY_IMAGE;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Picking features is not currently supported by this imagery provider, so this function simply returns
|
||||
* undefined.
|
||||
*
|
||||
* @param {number} x The tile X coordinate.
|
||||
* @param {number} y The tile Y coordinate.
|
||||
* @param {number} level The tile level.
|
||||
* @param {number} longitude The longitude at which to pick features.
|
||||
* @param {number} latitude The latitude at which to pick features.
|
||||
* @return {undefined} Undefined since picking is not supported.
|
||||
*/
|
||||
BingMapsImageryProvider.prototype.pickFeatures = function (
|
||||
x,
|
||||
y,
|
||||
level,
|
||||
longitude,
|
||||
latitude,
|
||||
) {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a tiles (x, y, level) position into a quadkey used to request an image
|
||||
* from a Bing Maps server.
|
||||
*
|
||||
* @param {number} x The tile's x coordinate.
|
||||
* @param {number} y The tile's y coordinate.
|
||||
* @param {number} level The tile's zoom level.
|
||||
*
|
||||
* @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
|
||||
* @see BingMapsImageryProvider#quadKeyToTileXY
|
||||
*/
|
||||
BingMapsImageryProvider.tileXYToQuadKey = function (x, y, level) {
|
||||
let quadkey = "";
|
||||
for (let i = level; i >= 0; --i) {
|
||||
const bitmask = 1 << i;
|
||||
let digit = 0;
|
||||
|
||||
if ((x & bitmask) !== 0) {
|
||||
digit |= 1;
|
||||
}
|
||||
|
||||
if ((y & bitmask) !== 0) {
|
||||
digit |= 2;
|
||||
}
|
||||
|
||||
quadkey += digit;
|
||||
}
|
||||
return quadkey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a tile's quadkey used to request an image from a Bing Maps server into the
|
||||
* (x, y, level) position.
|
||||
*
|
||||
* @param {string} quadkey The tile's quad key
|
||||
*
|
||||
* @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
|
||||
* @see BingMapsImageryProvider#tileXYToQuadKey
|
||||
*/
|
||||
BingMapsImageryProvider.quadKeyToTileXY = function (quadkey) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
const level = quadkey.length - 1;
|
||||
for (let i = level; i >= 0; --i) {
|
||||
const bitmask = 1 << i;
|
||||
const digit = +quadkey[level - i];
|
||||
|
||||
if ((digit & 1) !== 0) {
|
||||
x |= bitmask;
|
||||
}
|
||||
|
||||
if ((digit & 2) !== 0) {
|
||||
y |= bitmask;
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
level: level,
|
||||
};
|
||||
};
|
||||
|
||||
BingMapsImageryProvider._logoUrl = undefined;
|
||||
|
||||
Object.defineProperties(BingMapsImageryProvider, {
|
||||
/**
|
||||
* Gets or sets the URL to the Bing logo for display in the credit.
|
||||
* @memberof BingMapsImageryProvider
|
||||
* @type {string}
|
||||
*/
|
||||
logoUrl: {
|
||||
get: function () {
|
||||
if (!defined(BingMapsImageryProvider._logoUrl)) {
|
||||
BingMapsImageryProvider._logoUrl = buildModuleUrl(
|
||||
"Assets/Images/bing_maps_credit.png",
|
||||
);
|
||||
}
|
||||
return BingMapsImageryProvider._logoUrl;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
BingMapsImageryProvider._logoUrl = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function buildImageResource(imageryProvider, x, y, level, request) {
|
||||
const imageUrl = imageryProvider._imageUrlTemplate;
|
||||
|
||||
const subdomains = imageryProvider._imageUrlSubdomains;
|
||||
const subdomainIndex = (x + y + level) % subdomains.length;
|
||||
|
||||
return imageryProvider._resource.getDerivedResource({
|
||||
url: imageUrl,
|
||||
request: request,
|
||||
templateValues: {
|
||||
quadkey: BingMapsImageryProvider.tileXYToQuadKey(x, y, level),
|
||||
subdomain: subdomains[subdomainIndex],
|
||||
culture: imageryProvider._culture,
|
||||
},
|
||||
queryParameters: {
|
||||
// this parameter tells the Bing servers to send a zero-length response
|
||||
// instead of a placeholder image for missing tiles.
|
||||
n: "z",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const intersectionScratch = new Rectangle();
|
||||
|
||||
function getRectangleAttribution(attributionList, level, rectangle) {
|
||||
// Bing levels start at 1, while ours start at 0.
|
||||
++level;
|
||||
|
||||
const result = [];
|
||||
|
||||
for (
|
||||
let attributionIndex = 0, attributionLength = attributionList.length;
|
||||
attributionIndex < attributionLength;
|
||||
++attributionIndex
|
||||
) {
|
||||
const attribution = attributionList[attributionIndex];
|
||||
const coverageAreas = attribution.coverageAreas;
|
||||
|
||||
let included = false;
|
||||
|
||||
for (
|
||||
let areaIndex = 0, areaLength = attribution.coverageAreas.length;
|
||||
!included && areaIndex < areaLength;
|
||||
++areaIndex
|
||||
) {
|
||||
const area = coverageAreas[areaIndex];
|
||||
if (level >= area.zoomMin && level <= area.zoomMax) {
|
||||
const intersection = Rectangle.intersection(
|
||||
rectangle,
|
||||
area.bbox,
|
||||
intersectionScratch,
|
||||
);
|
||||
if (defined(intersection)) {
|
||||
included = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (included) {
|
||||
result.push(attribution.credit);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Exposed for testing
|
||||
BingMapsImageryProvider._metadataCache = {};
|
||||
export default BingMapsImageryProvider;
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* The types of imagery provided by Bing Maps.
|
||||
*
|
||||
* @enum {number}
|
||||
*
|
||||
* @see BingMapsImageryProvider
|
||||
*/
|
||||
const BingMapsStyle = {
|
||||
/**
|
||||
* Aerial imagery.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
AERIAL: "Aerial",
|
||||
|
||||
/**
|
||||
* Aerial imagery with a road overlay.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @deprecated See https://github.com/CesiumGS/cesium/issues/7128.
|
||||
* Use `BingMapsStyle.AERIAL_WITH_LABELS_ON_DEMAND` instead
|
||||
*/
|
||||
AERIAL_WITH_LABELS: "AerialWithLabels",
|
||||
|
||||
/**
|
||||
* Aerial imagery with a road overlay.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
AERIAL_WITH_LABELS_ON_DEMAND: "AerialWithLabelsOnDemand",
|
||||
|
||||
/**
|
||||
* Roads without additional imagery.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @deprecated See https://github.com/CesiumGS/cesium/issues/7128.
|
||||
* Use `BingMapsStyle.ROAD_ON_DEMAND` instead
|
||||
*/
|
||||
ROAD: "Road",
|
||||
|
||||
/**
|
||||
* Roads without additional imagery.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
ROAD_ON_DEMAND: "RoadOnDemand",
|
||||
|
||||
/**
|
||||
* A dark version of the road maps.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
CANVAS_DARK: "CanvasDark",
|
||||
|
||||
/**
|
||||
* A lighter version of the road maps.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
CANVAS_LIGHT: "CanvasLight",
|
||||
|
||||
/**
|
||||
* A grayscale version of the road maps.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
CANVAS_GRAY: "CanvasGray",
|
||||
|
||||
/**
|
||||
* Ordnance Survey imagery. This imagery is visible only for the London, UK area.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
ORDNANCE_SURVEY: "OrdnanceSurvey",
|
||||
|
||||
/**
|
||||
* Collins Bart imagery.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
*/
|
||||
COLLINS_BART: "CollinsBart",
|
||||
};
|
||||
|
||||
Object.freeze(BingMapsStyle);
|
||||
|
||||
export default BingMapsStyle;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// @ts-check
|
||||
|
||||
import WebGLConstants from "../Core/WebGLConstants.js";
|
||||
|
||||
/**
|
||||
* Determines how two pixels' values are combined.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const BlendEquation = {
|
||||
/**
|
||||
* Pixel values are added componentwise. This is used in additive blending for translucency.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ADD: WebGLConstants.FUNC_ADD,
|
||||
|
||||
/**
|
||||
* Pixel values are subtracted componentwise (source - destination). This is used in alpha blending for translucency.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
SUBTRACT: WebGLConstants.FUNC_SUBTRACT,
|
||||
|
||||
/**
|
||||
* Pixel values are subtracted componentwise (destination - source).
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
REVERSE_SUBTRACT: WebGLConstants.FUNC_REVERSE_SUBTRACT,
|
||||
|
||||
/**
|
||||
* Pixel values are given to the minimum function (min(source, destination)).
|
||||
*
|
||||
* This equation operates on each pixel color component.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
MIN: WebGLConstants.MIN,
|
||||
|
||||
/**
|
||||
* Pixel values are given to the maximum function (max(source, destination)).
|
||||
*
|
||||
* This equation operates on each pixel color component.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
MAX: WebGLConstants.MAX,
|
||||
};
|
||||
|
||||
Object.freeze(BlendEquation);
|
||||
|
||||
export default BlendEquation;
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// @ts-check
|
||||
|
||||
import WebGLConstants from "../Core/WebGLConstants.js";
|
||||
|
||||
/**
|
||||
* Determines how blending factors are computed.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const BlendFunction = {
|
||||
/**
|
||||
* The blend factor is zero.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ZERO: WebGLConstants.ZERO,
|
||||
|
||||
/**
|
||||
* The blend factor is one.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE: WebGLConstants.ONE,
|
||||
|
||||
/**
|
||||
* The blend factor is the source color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
SOURCE_COLOR: WebGLConstants.SRC_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the source color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_SOURCE_COLOR: WebGLConstants.ONE_MINUS_SRC_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is the destination color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
DESTINATION_COLOR: WebGLConstants.DST_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the destination color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_DESTINATION_COLOR: WebGLConstants.ONE_MINUS_DST_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is the source alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
SOURCE_ALPHA: WebGLConstants.SRC_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the source alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_SOURCE_ALPHA: WebGLConstants.ONE_MINUS_SRC_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is the destination alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
DESTINATION_ALPHA: WebGLConstants.DST_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the destination alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_DESTINATION_ALPHA: WebGLConstants.ONE_MINUS_DST_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is the constant color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
CONSTANT_COLOR: WebGLConstants.CONSTANT_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the constant color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_CONSTANT_COLOR: WebGLConstants.ONE_MINUS_CONSTANT_COLOR,
|
||||
|
||||
/**
|
||||
* The blend factor is the constant alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
CONSTANT_ALPHA: WebGLConstants.CONSTANT_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is one minus the constant alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ONE_MINUS_CONSTANT_ALPHA: WebGLConstants.ONE_MINUS_CONSTANT_ALPHA,
|
||||
|
||||
/**
|
||||
* The blend factor is the saturated source alpha.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
SOURCE_ALPHA_SATURATE: WebGLConstants.SRC_ALPHA_SATURATE,
|
||||
};
|
||||
|
||||
Object.freeze(BlendFunction);
|
||||
|
||||
export default BlendFunction;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Determines how opaque and translucent parts of billboards, points, and labels are blended with the scene.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const BlendOption = {
|
||||
/**
|
||||
* The billboards, points, or labels in the collection are completely opaque.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
OPAQUE: 0,
|
||||
|
||||
/**
|
||||
* The billboards, points, or labels in the collection are completely translucent.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
TRANSLUCENT: 1,
|
||||
|
||||
/**
|
||||
* The billboards, points, or labels in the collection are both opaque and translucent.
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
OPAQUE_AND_TRANSLUCENT: 2,
|
||||
};
|
||||
|
||||
Object.freeze(BlendOption);
|
||||
|
||||
export default BlendOption;
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// @ts-check
|
||||
|
||||
import BlendEquation from "./BlendEquation.js";
|
||||
import BlendFunction from "./BlendFunction.js";
|
||||
|
||||
/**
|
||||
* The blending state combines {@link BlendEquation} and {@link BlendFunction} and the
|
||||
* <code>enabled</code> flag to define the full blending state for combining source and
|
||||
* destination fragments when rendering.
|
||||
* <p>
|
||||
* This is a helper when using custom render states with {@link Appearance#renderState}.
|
||||
* </p>
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const BlendingState = {
|
||||
/**
|
||||
* Blending is disabled.
|
||||
*
|
||||
* @type {object}
|
||||
* @constant
|
||||
*/
|
||||
DISABLED: Object.freeze({
|
||||
enabled: false,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Blending is enabled using alpha blending, <code>source(source.alpha) + destination(1 - source.alpha)</code>.
|
||||
*
|
||||
* @type {object}
|
||||
* @constant
|
||||
*/
|
||||
ALPHA_BLEND: Object.freeze({
|
||||
enabled: true,
|
||||
equationRgb: BlendEquation.ADD,
|
||||
equationAlpha: BlendEquation.ADD,
|
||||
functionSourceRgb: BlendFunction.SOURCE_ALPHA,
|
||||
functionSourceAlpha: BlendFunction.ONE,
|
||||
functionDestinationRgb: BlendFunction.ONE_MINUS_SOURCE_ALPHA,
|
||||
functionDestinationAlpha: BlendFunction.ONE_MINUS_SOURCE_ALPHA,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Blending is enabled using alpha blending with premultiplied alpha, <code>source + destination(1 - source.alpha)</code>.
|
||||
*
|
||||
* @type {object}
|
||||
* @constant
|
||||
*/
|
||||
PRE_MULTIPLIED_ALPHA_BLEND: Object.freeze({
|
||||
enabled: true,
|
||||
equationRgb: BlendEquation.ADD,
|
||||
equationAlpha: BlendEquation.ADD,
|
||||
functionSourceRgb: BlendFunction.ONE,
|
||||
functionSourceAlpha: BlendFunction.ONE,
|
||||
functionDestinationRgb: BlendFunction.ONE_MINUS_SOURCE_ALPHA,
|
||||
functionDestinationAlpha: BlendFunction.ONE_MINUS_SOURCE_ALPHA,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Blending is enabled using additive blending, <code>source(source.alpha) + destination</code>.
|
||||
*
|
||||
* @type {object}
|
||||
* @constant
|
||||
*/
|
||||
ADDITIVE_BLEND: Object.freeze({
|
||||
enabled: true,
|
||||
equationRgb: BlendEquation.ADD,
|
||||
equationAlpha: BlendEquation.ADD,
|
||||
functionSourceRgb: BlendFunction.SOURCE_ALPHA,
|
||||
functionSourceAlpha: BlendFunction.ONE,
|
||||
functionDestinationRgb: BlendFunction.ONE,
|
||||
functionDestinationAlpha: BlendFunction.ONE,
|
||||
}),
|
||||
};
|
||||
|
||||
Object.freeze(BlendingState);
|
||||
|
||||
export default BlendingState;
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
|
||||
/**
|
||||
* Utilities for parsing bounding volume semantics from 3D Tiles 1.1 metadata.
|
||||
*
|
||||
* @namespace BoundingVolumeSemantics
|
||||
* @private
|
||||
*/
|
||||
const BoundingVolumeSemantics = {};
|
||||
|
||||
/**
|
||||
* Parse the bounding volume-related semantics such as
|
||||
* <code>TILE_BOUNDING_BOX</code> and <code>CONTENT_BOUNDING_BOX</code> from
|
||||
* implicit tile or content metadata. Results are returned as a JSON object for
|
||||
* use when transcoding tiles (see {@link Implicit3DTileContent}).
|
||||
* <p>
|
||||
* Bounding volumes are checked in the order box, region, then sphere. Only
|
||||
* the first valid bounding volume is returned.
|
||||
* </p>
|
||||
* <p>
|
||||
* This handles both tile and content bounding volumes, as the only difference
|
||||
* is the prefix. e.g. <code>TILE_BOUNDING_BOX</code> and
|
||||
* <code>CONTENT_BOUNDING_BOX</code> have the same memory layout.
|
||||
* </p>
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata/Semantics|3D Metadata Semantic Reference} for the various bounding volumes and minimum/maximum heights.
|
||||
*
|
||||
* @param {string} prefix Either "TILE" or "CONTENT"
|
||||
* @param {TileMetadata|ContentMetadata} metadata The metadata object for looking up values by semantic. In practice, this will typically be a {@link ImplicitMetadataView}
|
||||
* @return {object} An object containing the bounding volume, and any minimum or maximum height.
|
||||
*
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
BoundingVolumeSemantics.parseAllBoundingVolumeSemantics = function (
|
||||
prefix,
|
||||
metadata,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("prefix", prefix);
|
||||
if (prefix !== "TILE" && prefix !== "CONTENT") {
|
||||
throw new DeveloperError("prefix must be either 'TILE' or 'CONTENT'");
|
||||
}
|
||||
Check.typeOf.object("metadata", metadata);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
return {
|
||||
boundingVolume: BoundingVolumeSemantics.parseBoundingVolumeSemantic(
|
||||
prefix,
|
||||
metadata,
|
||||
),
|
||||
minimumHeight: BoundingVolumeSemantics._parseMinimumHeight(
|
||||
prefix,
|
||||
metadata,
|
||||
),
|
||||
maximumHeight: BoundingVolumeSemantics._parseMaximumHeight(
|
||||
prefix,
|
||||
metadata,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the bounding volume from tile or content metadata. If the metadata
|
||||
* specify multiple bounding volumes, only the first one is returned. Bounding
|
||||
* volumes are checked in the order box, region, then sphere.
|
||||
* <p>
|
||||
* This handles both tile and content bounding volumes, as the only difference
|
||||
* is the prefix. e.g. <code>TILE_BOUNDING_BOX</code> and
|
||||
* <code>CONTENT_BOUNDING_BOX</code> have the same memory layout.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} prefix Either "TILE" or "CONTENT"
|
||||
* @param {TileMetadata|ContentMetadata} metadata The metadata for looking up values
|
||||
* @return {object} An object representing the JSON description of the tile or content bounding volume
|
||||
* @private
|
||||
*/
|
||||
BoundingVolumeSemantics.parseBoundingVolumeSemantic = function (
|
||||
prefix,
|
||||
metadata,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("prefix", prefix);
|
||||
if (prefix !== "TILE" && prefix !== "CONTENT") {
|
||||
throw new DeveloperError("prefix must be either 'TILE' or 'CONTENT'");
|
||||
}
|
||||
Check.typeOf.object("metadata", metadata);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const boundingBoxSemantic = `${prefix}_BOUNDING_BOX`;
|
||||
const boundingBox = metadata.getPropertyBySemantic(boundingBoxSemantic);
|
||||
|
||||
if (defined(boundingBox)) {
|
||||
return {
|
||||
box: boundingBox,
|
||||
};
|
||||
}
|
||||
|
||||
const boundingRegionSemantic = `${prefix}_BOUNDING_REGION`;
|
||||
const boundingRegion = metadata.getPropertyBySemantic(boundingRegionSemantic);
|
||||
|
||||
if (defined(boundingRegion)) {
|
||||
return {
|
||||
region: boundingRegion,
|
||||
};
|
||||
}
|
||||
|
||||
const boundingSphereSemantic = `${prefix}_BOUNDING_SPHERE`;
|
||||
const boundingSphere = metadata.getPropertyBySemantic(boundingSphereSemantic);
|
||||
|
||||
if (defined(boundingSphere)) {
|
||||
// ARRAY with 4 elements is automatically converted to a Cartesian4
|
||||
return {
|
||||
sphere: boundingSphere,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the minimum height from tile or content metadata. This is used for making
|
||||
* tighter quadtree bounds for implicit tiling. This works for both
|
||||
* <code>TILE_MINIMUM_HEIGHT</code> and <code>CONTENT_MINIMUM_HEIGHT</code>
|
||||
*
|
||||
* @param {string} prefix Either "TILE" or "CONTENT"
|
||||
* @param {TileMetadata|ContentMetadata} metadata The metadata for looking up values
|
||||
* @return {number} The minimum height
|
||||
* @private
|
||||
*/
|
||||
BoundingVolumeSemantics._parseMinimumHeight = function (prefix, metadata) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("prefix", prefix);
|
||||
if (prefix !== "TILE" && prefix !== "CONTENT") {
|
||||
throw new DeveloperError("prefix must be either 'TILE' or 'CONTENT'");
|
||||
}
|
||||
Check.typeOf.object("metadata", metadata);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const minimumHeightSemantic = `${prefix}_MINIMUM_HEIGHT`;
|
||||
return metadata.getPropertyBySemantic(minimumHeightSemantic);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the maximum height from tile or content metadata. This is used for
|
||||
* making tighter quadtree bounds for implicit tiling. This works for both
|
||||
* <code>TILE_MAXIMUM_HEIGHT</code> and <code>CONTENT_MAXIMUM_HEIGHT</code>
|
||||
*
|
||||
* @param {string} prefix Either "TILE" or "CONTENT"
|
||||
* @param {TileMetadata|ContentMetadata} metadata The metadata for looking up values
|
||||
* @return {number} The maximum height
|
||||
* @private
|
||||
*/
|
||||
BoundingVolumeSemantics._parseMaximumHeight = function (prefix, metadata) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("prefix", prefix);
|
||||
if (prefix !== "TILE" && prefix !== "CONTENT") {
|
||||
throw new DeveloperError("prefix must be either 'TILE' or 'CONTENT'");
|
||||
}
|
||||
Check.typeOf.object("metadata", metadata);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const maximumHeightSemantic = `${prefix}_MAXIMUM_HEIGHT`;
|
||||
return metadata.getPropertyBySemantic(maximumHeightSemantic);
|
||||
};
|
||||
|
||||
export default BoundingVolumeSemantics;
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
const defaultDimensions = new Cartesian3(1.0, 1.0, 1.0);
|
||||
|
||||
/**
|
||||
* A ParticleEmitter that emits particles within a box.
|
||||
* Particles will be positioned randomly within the box and have initial velocities emanating from the center of the box.
|
||||
*
|
||||
* @alias BoxEmitter
|
||||
* @constructor
|
||||
*
|
||||
* @param {Cartesian3} dimensions The width, height and depth dimensions of the box.
|
||||
*/
|
||||
function BoxEmitter(dimensions) {
|
||||
dimensions = dimensions ?? defaultDimensions;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("dimensions", dimensions);
|
||||
Check.typeOf.number.greaterThanOrEquals("dimensions.x", dimensions.x, 0.0);
|
||||
Check.typeOf.number.greaterThanOrEquals("dimensions.y", dimensions.y, 0.0);
|
||||
Check.typeOf.number.greaterThanOrEquals("dimensions.z", dimensions.z, 0.0);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._dimensions = Cartesian3.clone(dimensions);
|
||||
}
|
||||
|
||||
Object.defineProperties(BoxEmitter.prototype, {
|
||||
/**
|
||||
* The width, height and depth dimensions of the box in meters.
|
||||
* @memberof BoxEmitter.prototype
|
||||
* @type {Cartesian3}
|
||||
* @default new Cartesian3(1.0, 1.0, 1.0)
|
||||
*/
|
||||
dimensions: {
|
||||
get: function () {
|
||||
return this._dimensions;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("value", value);
|
||||
Check.typeOf.number.greaterThanOrEquals("value.x", value.x, 0.0);
|
||||
Check.typeOf.number.greaterThanOrEquals("value.y", value.y, 0.0);
|
||||
Check.typeOf.number.greaterThanOrEquals("value.z", value.z, 0.0);
|
||||
//>>includeEnd('debug');
|
||||
Cartesian3.clone(value, this._dimensions);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const scratchHalfDim = new Cartesian3();
|
||||
|
||||
/**
|
||||
* Initializes the given {Particle} by setting it's position and velocity.
|
||||
*
|
||||
* @private
|
||||
* @param {Particle} particle The particle to initialize.
|
||||
*/
|
||||
BoxEmitter.prototype.emit = function (particle) {
|
||||
const dim = this._dimensions;
|
||||
const halfDim = Cartesian3.multiplyByScalar(dim, 0.5, scratchHalfDim);
|
||||
|
||||
const x = CesiumMath.randomBetween(-halfDim.x, halfDim.x);
|
||||
const y = CesiumMath.randomBetween(-halfDim.y, halfDim.y);
|
||||
const z = CesiumMath.randomBetween(-halfDim.z, halfDim.z);
|
||||
|
||||
particle.position = Cartesian3.fromElements(x, y, z, particle.position);
|
||||
particle.velocity = Cartesian3.normalize(
|
||||
particle.position,
|
||||
particle.velocity,
|
||||
);
|
||||
};
|
||||
export default BoxEmitter;
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import BoundingRectangle from "../Core/BoundingRectangle.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import PixelFormat from "../Core/PixelFormat.js";
|
||||
import Framebuffer from "../Renderer/Framebuffer.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
import RenderState from "../Renderer/RenderState.js";
|
||||
import Sampler from "../Renderer/Sampler.js";
|
||||
import Texture from "../Renderer/Texture.js";
|
||||
import BrdfLutGeneratorFS from "../Shaders/BrdfLutGeneratorFS.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function BrdfLutGenerator() {
|
||||
this._colorTexture = undefined;
|
||||
this._drawCommand = undefined;
|
||||
}
|
||||
|
||||
Object.defineProperties(BrdfLutGenerator.prototype, {
|
||||
colorTexture: {
|
||||
get: function () {
|
||||
return this._colorTexture;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function createCommand(generator, context, framebuffer) {
|
||||
const drawCommand = context.createViewportQuadCommand(BrdfLutGeneratorFS, {
|
||||
framebuffer: framebuffer,
|
||||
renderState: RenderState.fromCache({
|
||||
viewport: new BoundingRectangle(0.0, 0.0, 256.0, 256.0),
|
||||
}),
|
||||
});
|
||||
|
||||
generator._drawCommand = drawCommand;
|
||||
}
|
||||
|
||||
BrdfLutGenerator.prototype.update = function (frameState) {
|
||||
if (!defined(this._colorTexture)) {
|
||||
const context = frameState.context;
|
||||
const colorTexture = new Texture({
|
||||
context: context,
|
||||
width: 256,
|
||||
height: 256,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
||||
sampler: Sampler.NEAREST,
|
||||
});
|
||||
|
||||
this._colorTexture = colorTexture;
|
||||
const framebuffer = new Framebuffer({
|
||||
context: context,
|
||||
colorTextures: [colorTexture],
|
||||
destroyAttachments: false,
|
||||
});
|
||||
|
||||
createCommand(this, context, framebuffer);
|
||||
this._drawCommand.execute(context);
|
||||
framebuffer.destroy();
|
||||
this._drawCommand.shaderProgram =
|
||||
this._drawCommand.shaderProgram &&
|
||||
this._drawCommand.shaderProgram.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
BrdfLutGenerator.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
BrdfLutGenerator.prototype.destroy = function () {
|
||||
this._colorTexture = this._colorTexture && this._colorTexture.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default BrdfLutGenerator;
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import ResourceLoader from "./ResourceLoader.js";
|
||||
import ResourceLoaderState from "./ResourceLoaderState.js";
|
||||
|
||||
/**
|
||||
* Loads an embedded or external buffer.
|
||||
* <p>
|
||||
* Implements the {@link ResourceLoader} interface.
|
||||
* </p>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class BufferLoader extends ResourceLoader {
|
||||
/**
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {Uint8Array} [options.typedArray] The typed array containing the embedded buffer contents. Mutually exclusive with options.resource.
|
||||
* @param {Resource} [options.resource] The {@link Resource} pointing to the external buffer. Mutually exclusive with options.typedArray.
|
||||
* @param {string} [options.cacheKey] The cache key of the resource.
|
||||
*
|
||||
* @exception {DeveloperError} One of options.typedArray and options.resource must be defined.
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
const typedArray = options.typedArray;
|
||||
const resource = options.resource;
|
||||
const cacheKey = options.cacheKey;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (defined(typedArray) === defined(resource)) {
|
||||
throw new DeveloperError(
|
||||
"One of options.typedArray and options.resource must be defined.",
|
||||
);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._typedArray = typedArray;
|
||||
this._resource = resource;
|
||||
this._cacheKey = cacheKey;
|
||||
this._state = ResourceLoaderState.UNLOADED;
|
||||
this._promise = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache key of the resource.
|
||||
*
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get cacheKey() {
|
||||
return this._cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The typed array containing the embedded buffer contents.
|
||||
*
|
||||
*
|
||||
* @type {Uint8Array}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get typedArray() {
|
||||
return this._typedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the resource.
|
||||
* @returns {Promise<BufferLoader>} A promise which resolves to the loader when the resource loading is completed.
|
||||
* @private
|
||||
*/
|
||||
async load() {
|
||||
if (defined(this._promise)) {
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
if (defined(this._typedArray)) {
|
||||
this._promise = Promise.resolve(this);
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
this._promise = loadExternalBuffer(this);
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposed for testing
|
||||
* @private
|
||||
*/
|
||||
static _fetchArrayBuffer(resource) {
|
||||
return resource.fetchArrayBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the resource.
|
||||
* @private
|
||||
*/
|
||||
unload() {
|
||||
this._typedArray = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadExternalBuffer(bufferLoader) {
|
||||
const resource = bufferLoader._resource;
|
||||
bufferLoader._state = ResourceLoaderState.LOADING;
|
||||
try {
|
||||
const arrayBuffer = await BufferLoader._fetchArrayBuffer(resource);
|
||||
if (bufferLoader.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bufferLoader._typedArray = new Uint8Array(arrayBuffer);
|
||||
bufferLoader._state = ResourceLoaderState.READY;
|
||||
return bufferLoader;
|
||||
} catch (error) {
|
||||
if (bufferLoader.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bufferLoader._state = ResourceLoaderState.FAILED;
|
||||
const errorMessage = `Failed to load external buffer: ${resource.url}`;
|
||||
throw bufferLoader.getError(errorMessage, error);
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferLoader;
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// @ts-check
|
||||
|
||||
import BufferPrimitive from "./BufferPrimitive.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import assert from "../Core/assert.js";
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
|
||||
/** @import BufferPointCollection from "./BufferPointCollection.js"; */
|
||||
|
||||
const { ERR_CAPACITY } = BufferPrimitiveCollection.Error;
|
||||
|
||||
const scratchCartesian = new Cartesian3();
|
||||
|
||||
/**
|
||||
* View bound to the underlying buffer data of a {@link BufferPointCollection}.
|
||||
*
|
||||
* <p>BufferPoint instances are {@link https://en.wikipedia.org/wiki/Flyweight_pattern|flyweights}:
|
||||
* a single BufferPoint instance can be temporarily bound to any conceptual
|
||||
* "point" in a BufferPointCollection, allowing very large collections to be
|
||||
* iterated and updated with a minimal memory footprint.</p>
|
||||
*
|
||||
* Represented as one (1) position.
|
||||
*
|
||||
* @see BufferPointCollection
|
||||
* @see BufferPointMaterial
|
||||
* @see BufferPrimitive
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
* @extends BufferPrimitive
|
||||
*/
|
||||
class BufferPoint extends BufferPrimitive {
|
||||
/**
|
||||
* @type {BufferPointCollection}
|
||||
* @ignore
|
||||
*/
|
||||
_collection = null;
|
||||
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
...BufferPrimitive.Layout,
|
||||
|
||||
/**
|
||||
* Offset in position array to current point vertex, number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
POSITION_OFFSET_U32: BufferPrimitive.Layout.__BYTE_LENGTH,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
__BYTE_LENGTH: BufferPrimitive.Layout.__BYTE_LENGTH + 4,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// LIFECYCLE
|
||||
|
||||
/**
|
||||
* Copies data from source point to result.
|
||||
*
|
||||
* @param {BufferPoint} point
|
||||
* @param {BufferPoint} result
|
||||
* @return {BufferPoint}
|
||||
* @override
|
||||
*/
|
||||
static clone(point, result) {
|
||||
super.clone(point, result);
|
||||
result.setPosition(point.getPosition(scratchCartesian));
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// GEOMETRY
|
||||
|
||||
/**
|
||||
* Offset in collection position array to position of this point, number
|
||||
* of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
get vertexOffset() {
|
||||
return this._getUint32(BufferPoint.Layout.POSITION_OFFSET_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this primitive. Always 1.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get vertexCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of this point.
|
||||
*
|
||||
* @param {Cartesian3} [result]
|
||||
* @returns {Cartesian3}
|
||||
*/
|
||||
getPosition(result) {
|
||||
const positionF64 = this._collection._positionView;
|
||||
// @ts-expect-error TODO(tsd-jsdoc): See https://github.com/CesiumGS/cesium/pull/13302.
|
||||
return Cartesian3.fromArray(positionF64, this.vertexOffset * 3, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of this point.
|
||||
*
|
||||
* @param {Cartesian3} position
|
||||
*/
|
||||
setPosition(position) {
|
||||
const collection = this._collection;
|
||||
const vertexOffset = this.vertexOffset;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(vertexOffset < collection.vertexCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
collection._positionView[vertexOffset * 3] = position.x;
|
||||
collection._positionView[vertexOffset * 3 + 1] = position.y;
|
||||
collection._positionView[vertexOffset * 3 + 2] = position.z;
|
||||
|
||||
this._dirty = true;
|
||||
collection._makeDirtyBoundingVolume();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the point. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
* @override
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
position: Cartesian3.pack(this.getPosition(), []),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPoint;
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// @ts-check
|
||||
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
import BufferPoint from "./BufferPoint.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import renderPoints from "./renderBufferPointCollection.js";
|
||||
import BufferPointMaterial from "./BufferPointMaterial.js";
|
||||
|
||||
/** @import BlendOption from "./BlendOption.js"; */
|
||||
/** @import BoundingSphere from "../Core/BoundingSphere.js"; */
|
||||
/** @import ComponentDatatype from "../Core/ComponentDatatype.js"; */
|
||||
/** @import Matrix4 from "../Core/Matrix4.js"; */
|
||||
/** @import FrameState from "./FrameState.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPointOptions
|
||||
* @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @property {boolean} [show=true]
|
||||
* @property {BufferPointMaterial} [material=BufferPointMaterial.DEFAULT_MATERIAL]
|
||||
* @property {number} [featureId]
|
||||
* @property {object} [pickObject]
|
||||
* @property {Cartesian3} [position=Cartesian3.ZERO]
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of points held in ArrayBuffer storage for performance and memory optimization.
|
||||
*
|
||||
* <p>Default buffer memory allocation is arbitrary, and collections cannot be resized,
|
||||
* so specific per-buffer capacities should be provided in the collection
|
||||
* constructor when available.</p>
|
||||
*
|
||||
* @example
|
||||
* const collection = new BufferPointCollection({primitiveCountMax: 1024});
|
||||
*
|
||||
* const point = new BufferPoint();
|
||||
* const material = new BufferPointMaterial({color: Color.WHITE});
|
||||
*
|
||||
* // Create a new point, temporarily bound to 'point' local variable.
|
||||
* collection.add({
|
||||
* position: new Cartesian3(0.0, 0.0, 0.0),
|
||||
* material
|
||||
* }, point);
|
||||
*
|
||||
* // Iterate over all points in collection, temporarily binding 'point'
|
||||
* // local variable to each, and updating point material.
|
||||
* for (let i = 0; i < collection.primitiveCount; i++) {
|
||||
* collection.get(i, point);
|
||||
* point.setMaterial(material);
|
||||
* }
|
||||
*
|
||||
* @see BufferPoint
|
||||
* @see BufferPrimitiveCollection
|
||||
* @extends BufferPrimitiveCollection<BufferPoint>
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPointCollection extends BufferPrimitiveCollection {
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @param {number} [options.primitiveCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {ComponentDatatype} [options.positionDatatype=ComponentDatatype.DOUBLE]
|
||||
* @param {boolean} [options.positionNormalized=false]
|
||||
* @param {boolean} [options.show=true]
|
||||
* @param {boolean} [options.allowPicking=false] When <code>true</code>, primitives are pickable with {@link Scene#pick}. When <code>false</code>, memory and initialization cost are lower.
|
||||
* @param {BoundingSphere} [options.boundingVolume] Bounding volume, in world space, for the collection. When
|
||||
* unspecified, a bounding volume is computed automatically and updated when primitive positions change. When
|
||||
* specified, users are responsible for updating bounding volume as needed. Pre-computing the bounding volume
|
||||
* manually, and updating it only as needed, will improve performance for larger dynamic collections.
|
||||
* @param {boolean} [options.debugShowBoundingVolume=false]
|
||||
* @param {BlendOption} [options.blendOption=BlendOption.TRANSLUCENT]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
super({ ...options, vertexCountMax: options.primitiveCountMax });
|
||||
}
|
||||
|
||||
_getCollectionClass() {
|
||||
return BufferPointCollection;
|
||||
}
|
||||
|
||||
_getPrimitiveClass() {
|
||||
return BufferPoint;
|
||||
}
|
||||
|
||||
_getMaterialClass() {
|
||||
return BufferPointMaterial;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// COLLECTION LIFECYCLE
|
||||
|
||||
/**
|
||||
* @param {BufferPointCollection} collection
|
||||
* @returns {BufferPointCollection}
|
||||
* @override
|
||||
* @ignore
|
||||
*/
|
||||
static _cloneEmpty(collection) {
|
||||
return new BufferPointCollection({
|
||||
primitiveCountMax: collection.primitiveCountMax,
|
||||
positionDatatype: collection.positionDatatype,
|
||||
positionNormalized: collection.positionNormalized,
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// PRIMITIVE LIFECYCLE
|
||||
|
||||
/**
|
||||
* Adds a new point to the collection, with the specified options. A
|
||||
* {@link BufferPoint} instance is linked to the new point, using
|
||||
* the 'result' argument if given, or a new instance if not. For repeated
|
||||
* calls, prefer to reuse a single BufferPoint instance rather than
|
||||
* allocating a new instance on each call.
|
||||
*
|
||||
* @param {BufferPointOptions} options
|
||||
* @param {BufferPoint} result
|
||||
* @returns {BufferPoint}
|
||||
* @override
|
||||
*/
|
||||
add(options, result = new BufferPoint()) {
|
||||
super.add(options, result);
|
||||
|
||||
result._setUint32(
|
||||
BufferPoint.Layout.POSITION_OFFSET_U32,
|
||||
this._positionCount++,
|
||||
);
|
||||
result.setPosition(options.position ?? Cartesian3.ZERO);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// RENDER
|
||||
|
||||
/**
|
||||
* @param {FrameState} frameState
|
||||
* @ignore
|
||||
*/
|
||||
update(frameState) {
|
||||
super.update(frameState);
|
||||
|
||||
const passes = frameState.passes;
|
||||
if (this.show && (passes.render || passes.pick)) {
|
||||
this._renderContext = renderPoints(this, frameState, this._renderContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPointCollection;
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// @ts-check
|
||||
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js";
|
||||
|
||||
/** @import Color from "../Core/Color.js"; */
|
||||
/** @import BufferPoint from "./BufferPoint.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPointMaterialOptions
|
||||
* @property {Color} [color=Color.WHITE] Color of fill.
|
||||
* @property {Color} [outlineColor=Color.WHITE] Color of outline.
|
||||
* @property {number} [outlineWidth=0.0] Width of outline, 0-255px.
|
||||
* @property {number} [size=1.0] Size of point, 0-255px.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Material description for a {@link BufferPoint}.
|
||||
*
|
||||
* <p>BufferPointMaterial objects are {@link Packable|packable}, stored
|
||||
* when calling {@link BufferPoint#setMaterial}. Subsequent changes to the
|
||||
* material will not affect the point until setMaterial() is called again.</p>
|
||||
*
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
* @extends BufferPrimitiveMaterial
|
||||
*/
|
||||
class BufferPointMaterial extends BufferPrimitiveMaterial {
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
...BufferPrimitiveMaterial.Layout,
|
||||
SIZE_U8: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH,
|
||||
__BYTE_LENGTH: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {BufferPointMaterial}
|
||||
* @ignore
|
||||
*/
|
||||
static DEFAULT_MATERIAL = Object.freeze(new BufferPointMaterial());
|
||||
|
||||
/**
|
||||
* @param {BufferPointMaterialOptions} [options]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
super(options);
|
||||
|
||||
/**
|
||||
* Size of point, 0-255px.
|
||||
* @type {number}
|
||||
*/
|
||||
this.size = options.size ?? 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {BufferPointMaterial} material
|
||||
* @param {DataView} view
|
||||
* @param {number} byteOffset
|
||||
* @override
|
||||
*/
|
||||
static pack(material, view, byteOffset) {
|
||||
super.pack(material, view, byteOffset);
|
||||
view.setUint8(this.Layout.SIZE_U8 + byteOffset, material.size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {DataView} view
|
||||
* @param {number} byteOffset
|
||||
* @param {BufferPointMaterial} result
|
||||
* @returns {BufferPointMaterial}
|
||||
* @override
|
||||
*/
|
||||
static unpack(view, byteOffset, result) {
|
||||
super.unpack(view, byteOffset, result);
|
||||
result.size = view.getUint8(this.Layout.SIZE_U8 + byteOffset);
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the material. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
*/
|
||||
toJSON() {
|
||||
return { ...super.toJSON(), size: this.size };
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPointMaterial;
|
||||
+517
@@ -0,0 +1,517 @@
|
||||
// @ts-check
|
||||
|
||||
import assert from "../Core/assert.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import BufferPrimitive from "./BufferPrimitive.js";
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
|
||||
/** @import { TypedArray, TypedArrayConstructor } from "../Core/globalTypes.js"; */
|
||||
/** @import BufferPolygonCollection from "./BufferPolygonCollection.js"; */
|
||||
|
||||
const { ERR_CAPACITY, ERR_RESIZE, ERR_OUT_OF_RANGE } =
|
||||
BufferPrimitiveCollection.Error;
|
||||
|
||||
/**
|
||||
* View bound to the underlying buffer data of a {@link BufferPolygonCollection}.
|
||||
*
|
||||
* <p>BufferPolygon instances are {@link https://en.wikipedia.org/wiki/Flyweight_pattern|flyweights}:
|
||||
* a single BufferPolygon instance can be temporarily bound to any conceptual
|
||||
* "polygon" in a BufferPolygonCollection, allowing very large collections to be
|
||||
* iterated and updated with a minimal memory footprint.</p>
|
||||
*
|
||||
* <p>Represented as one (1) external linear ring of three (3) or more positions.
|
||||
* May optionally define one or more internal linear rings ("holes") within the
|
||||
* polygon. Each hole is represented as a single index into the positions array,
|
||||
* where the vertex at that index is the start of an internal linear ring that
|
||||
* continues along the following vertices until reaching either the vertex
|
||||
* index of the next hole, or the end of the vertex list. Stores a precomputed
|
||||
* triangulation, represented as three vertex indices per triangle.</p>
|
||||
*
|
||||
* @see BufferPolygonCollection
|
||||
* @see BufferPolygonMaterial
|
||||
* @see BufferPrimitive
|
||||
* @extends BufferPrimitive
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPolygon extends BufferPrimitive {
|
||||
/**
|
||||
* @type {BufferPolygonCollection}
|
||||
* @ignore
|
||||
*/
|
||||
_collection = null;
|
||||
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
...BufferPrimitive.Layout,
|
||||
|
||||
/**
|
||||
* Offset in collection position array to first vertex in polygon, number
|
||||
* of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
POSITION_OFFSET_U32: BufferPrimitive.Layout.__BYTE_LENGTH,
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this polygon, number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
POSITION_COUNT_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 4,
|
||||
|
||||
/**
|
||||
* Offset in collection holes array to first hole in polygon, number of
|
||||
* integer elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
HOLE_OFFSET_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 8,
|
||||
|
||||
/**
|
||||
* Count of holes (indices) in this polygon.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
HOLE_COUNT_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 12,
|
||||
|
||||
/**
|
||||
* Offset in collection triangles array to first triangle in polygon,
|
||||
* number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
TRIANGLE_OFFSET_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 16,
|
||||
|
||||
/**
|
||||
* Count of triangles in this polygon, number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
TRIANGLE_COUNT_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 20,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
__BYTE_LENGTH: BufferPrimitive.Layout.__BYTE_LENGTH + 24,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// LIFECYCLE
|
||||
|
||||
/**
|
||||
* Copies data from source polygon to result. If the result polygon is not
|
||||
* new (the last polygon in the collection) then source and result polygons
|
||||
* must have the same vertex counts, hole counts, and triangle counts.
|
||||
*
|
||||
* @param {BufferPolygon} polygon
|
||||
* @param {BufferPolygon} result
|
||||
* @return {BufferPolygon}
|
||||
* @override
|
||||
*/
|
||||
static clone(polygon, result) {
|
||||
super.clone(polygon, result);
|
||||
result.setPositions(polygon.getPositions());
|
||||
result.setHoles(polygon.getHoles());
|
||||
result.setTriangles(polygon.getTriangles());
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// GEOMETRY
|
||||
|
||||
/**
|
||||
* Offset in collection position array to first vertex in polygon, number
|
||||
* of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
get vertexOffset() {
|
||||
return this._getUint32(BufferPolygon.Layout.POSITION_OFFSET_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this polygon, including both outer ring and
|
||||
* internal rings (holes), number of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get vertexCount() {
|
||||
return this._getUint32(BufferPolygon.Layout.POSITION_COUNT_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array view of this polygon's vertex positions. If 'result'
|
||||
* argument is given, vertex positions are written to that array and returned.
|
||||
* Otherwise, returns an ArrayView on collection memory — changes to this array
|
||||
* will not trigger render updates, which requires `.setPositions()`.
|
||||
*
|
||||
* @param {TypedArray} [result]
|
||||
* return {TypedArray}
|
||||
*/
|
||||
getPositions(result) {
|
||||
return this._getPositionsRange(0, this.vertexCount, result);
|
||||
}
|
||||
|
||||
/** @param {TypedArray} positions */
|
||||
setPositions(positions) {
|
||||
const collection = this._collection;
|
||||
const vertexOffset = this.vertexOffset;
|
||||
const srcCount = this.vertexCount;
|
||||
const dstCount = positions.length / 3;
|
||||
const collectionCount = collection.vertexCount + dstCount - srcCount;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(srcCount === dstCount || this._isResizable(), ERR_RESIZE);
|
||||
assert(collectionCount <= collection.vertexCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
collection._positionCount = collectionCount;
|
||||
this._setUint32(BufferPolygon.Layout.POSITION_COUNT_U32, dstCount);
|
||||
|
||||
const positionView = collection._positionView;
|
||||
for (let i = 0; i < dstCount; i++) {
|
||||
positionView[(vertexOffset + i) * 3] = positions[i * 3];
|
||||
positionView[(vertexOffset + i) * 3 + 1] = positions[i * 3 + 1];
|
||||
positionView[(vertexOffset + i) * 3 + 2] = positions[i * 3 + 2];
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
collection._makeDirtyBoundingVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset in collection position array to first vertex in polygon's outer
|
||||
* linear ring, number of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get outerVertexOffset() {
|
||||
return this.vertexOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this polygon's outer linear ring, number
|
||||
* of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get outerVertexCount() {
|
||||
if (this.holeCount > 0) {
|
||||
return this.getHoles()[0];
|
||||
}
|
||||
return this.vertexCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array view of this polygon's outer linear ring vertex positions.
|
||||
* If 'result' argument is given, vertex positions are written to that array
|
||||
* and returned. Otherwise, returns an ArrayView on collection memory —
|
||||
* changes to this array will not trigger render updates, which requires
|
||||
* `.setPositions()`.
|
||||
*
|
||||
* @param {TypedArray} [result]
|
||||
* @returns {TypedArray}
|
||||
*/
|
||||
getOuterPositions(result) {
|
||||
return this._getPositionsRange(0, this.outerVertexCount, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset in collection holes array to first hole in polygon, number of
|
||||
* integer elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
get holeOffset() {
|
||||
return this._getUint32(BufferPolygon.Layout.HOLE_OFFSET_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of holes (indices) in this polygon.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get holeCount() {
|
||||
return this._getUint32(BufferPolygon.Layout.HOLE_COUNT_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this polygon's hole indices, with each hole represented as a single
|
||||
* offset into this polygon's positions array. Each hole implicitly
|
||||
* continues along an internal linear ring from that vertex offset until
|
||||
* reaching either the end of the positions array, or the next hole offset.
|
||||
*
|
||||
* If 'result' argument is given, hole indices are written to that array and
|
||||
* returned. Otherwise, returns an ArrayView on collection memory — changes
|
||||
* to this array will not trigger render updates, which requires `.setHoles()`.
|
||||
*
|
||||
* @param {TypedArray} [result]
|
||||
* @returns {TypedArray}
|
||||
*/
|
||||
getHoles(result) {
|
||||
const { holeOffset, holeCount } = this;
|
||||
const holeIndexView = this._collection._holeIndexView;
|
||||
|
||||
if (!defined(result)) {
|
||||
const byteOffset =
|
||||
holeIndexView.byteOffset + holeOffset * holeIndexView.BYTES_PER_ELEMENT;
|
||||
const TypedArray = /** @type {TypedArrayConstructor} */ (
|
||||
holeIndexView.constructor
|
||||
);
|
||||
return new TypedArray(
|
||||
/** @type {ArrayBuffer} */ (holeIndexView.buffer),
|
||||
byteOffset,
|
||||
holeCount,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < holeCount; i++) {
|
||||
result[i] = holeIndexView[holeOffset + i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this polygon's hole indices, with holes represented as a single
|
||||
* offset into this polygon's positions array. Each hole implicitly
|
||||
* continues along an internal linear ring from that vertex offset until
|
||||
* reaching either the end of the positions array, or the next hole offset.
|
||||
*
|
||||
* @param {TypedArray} holes
|
||||
*/
|
||||
setHoles(holes) {
|
||||
const collection = this._collection;
|
||||
const holeOffset = this.holeOffset;
|
||||
const srcCount = this.holeCount;
|
||||
const dstCount = holes.length;
|
||||
const collectionCount = collection.holeCount + dstCount - srcCount;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(srcCount === dstCount || this._isResizable(), ERR_RESIZE);
|
||||
assert(collectionCount <= collection.holeCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
collection._holeCount = collectionCount;
|
||||
this._setUint32(BufferPolygon.Layout.HOLE_COUNT_U32, dstCount);
|
||||
|
||||
const holeIndexView = collection._holeIndexView;
|
||||
for (let i = 0; i < dstCount; i++) {
|
||||
holeIndexView[holeOffset + i] = holes[i];
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of (VEC3) vertices in the specified hole.
|
||||
*
|
||||
* @param {number} holeIndex
|
||||
* @returns {number}
|
||||
*/
|
||||
getHoleVertexCount(holeIndex) {
|
||||
const holes = this.getHoles();
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number.greaterThanOrEquals("holeIndex", holeIndex, 0);
|
||||
Check.typeOf.number.lessThan("holeIndex", holeIndex, holes.length);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const holeVertexOffset = holes[holeIndex];
|
||||
return holeIndex === holes.length - 1
|
||||
? this.vertexCount - holeVertexOffset
|
||||
: holes[holeIndex + 1] - holeVertexOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array view of the inner linear ring vertex positions for the
|
||||
* specified hole. If 'result' argument is given, vertex positions are written
|
||||
* to that array and returned. Otherwise, returns an ArrayView on collection
|
||||
* memory — changes to this array will not trigger render updates, which
|
||||
* requires `.setPositions()`.
|
||||
*
|
||||
* @param {number} holeIndex
|
||||
* @param {TypedArray} [result]
|
||||
* return {TypedArray}
|
||||
*/
|
||||
getHolePositions(holeIndex, result) {
|
||||
const holes = this.getHoles();
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number.greaterThanOrEquals("holeIndex", holeIndex, 0);
|
||||
Check.typeOf.number.lessThan("holeIndex", holeIndex, holes.length);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const holeVertexOffset = holes[holeIndex];
|
||||
const holeVertexCount = this.getHoleVertexCount(holeIndex);
|
||||
return this._getPositionsRange(holeVertexOffset, holeVertexCount, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper for accessing vertex positions. 'vertexOffset' argument
|
||||
* is relative to the start of the polygon's vertex block, with 0 being
|
||||
* the first vertex in the polygon. If 'result' argument is given, the
|
||||
* requested range of vertices are written to the result array and returned.
|
||||
* Otherwise, returns an ArrayView on collection memory.
|
||||
*
|
||||
* @param {number} vertexOffset
|
||||
* @param {number} vertexCount
|
||||
* @param {TypedArray} [result]
|
||||
* @returns {TypedArray}
|
||||
* @private
|
||||
*/
|
||||
_getPositionsRange(vertexOffset, vertexCount, result) {
|
||||
const collection = this._collection;
|
||||
const positionView = this._collection._positionView;
|
||||
|
||||
const collectionVertexOffset = this.vertexOffset + vertexOffset;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(collectionVertexOffset >= 0, ERR_OUT_OF_RANGE);
|
||||
assert(collectionVertexOffset < collection.vertexCount, ERR_OUT_OF_RANGE);
|
||||
assert(vertexCount > 0, ERR_OUT_OF_RANGE);
|
||||
assert(vertexCount <= this.vertexCount, ERR_OUT_OF_RANGE);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(result)) {
|
||||
const byteOffset =
|
||||
positionView.byteOffset +
|
||||
collectionVertexOffset * 3 * positionView.BYTES_PER_ELEMENT;
|
||||
const TypedArray = /** @type {TypedArrayConstructor} */ (
|
||||
positionView.constructor
|
||||
);
|
||||
return new TypedArray(
|
||||
/** @type {ArrayBuffer} */ (positionView.buffer),
|
||||
byteOffset,
|
||||
vertexCount * 3,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < vertexCount; i++) {
|
||||
result[i * 3] = positionView[(collectionVertexOffset + i) * 3];
|
||||
result[i * 3 + 1] = positionView[(collectionVertexOffset + i) * 3 + 1];
|
||||
result[i * 3 + 2] = positionView[(collectionVertexOffset + i) * 3 + 2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset in collection triangles array to first triangle in polygon,
|
||||
* number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
get triangleOffset() {
|
||||
return this._getUint32(BufferPolygon.Layout.TRIANGLE_OFFSET_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of triangles in this polygon, number of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get triangleCount() {
|
||||
return this._getUint32(BufferPolygon.Layout.TRIANGLE_COUNT_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array view of this polygon's triangle indices, represented as
|
||||
* three vertex indices per triangle.
|
||||
*
|
||||
* If 'result' argument is given, triangle indices are written to that array
|
||||
* and returned. Otherwise, returns an ArrayView on collection memory —
|
||||
* changes to this array will not trigger render updates, which requires
|
||||
* `.setTriangles()`.
|
||||
*
|
||||
* @param {TypedArray} [result]
|
||||
* @returns {TypedArray}
|
||||
*/
|
||||
getTriangles(result) {
|
||||
const { triangleOffset, triangleCount } = this;
|
||||
const indices = this._collection._triangleIndexView;
|
||||
|
||||
if (!defined(result)) {
|
||||
const byteOffset =
|
||||
indices.byteOffset + triangleOffset * 3 * indices.BYTES_PER_ELEMENT;
|
||||
const TypedArray = /** @type {TypedArrayConstructor} */ (
|
||||
indices.constructor
|
||||
);
|
||||
return new TypedArray(
|
||||
/** @type {ArrayBuffer} */ (indices.buffer),
|
||||
byteOffset,
|
||||
triangleCount * 3,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < triangleCount; i++) {
|
||||
result[i * 3] = indices[(triangleOffset + i) * 3];
|
||||
result[i * 3 + 1] = indices[(triangleOffset + i) * 3 + 1];
|
||||
result[i * 3 + 2] = indices[(triangleOffset + i) * 3 + 2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this polygon's triangle indices, represented as three vertex indices
|
||||
* per triangle.
|
||||
*
|
||||
* @param {TypedArray} indices
|
||||
*/
|
||||
setTriangles(indices) {
|
||||
const collection = this._collection;
|
||||
const triangleOffset = this.triangleOffset;
|
||||
const srcCount = this.triangleCount;
|
||||
const dstCount = indices.length / 3;
|
||||
const collectionCount = collection.triangleCount + dstCount - srcCount;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(srcCount === dstCount || this._isResizable(), ERR_RESIZE);
|
||||
assert(collectionCount <= collection.triangleCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
collection._triangleCount += dstCount - srcCount;
|
||||
this._setUint32(BufferPolygon.Layout.TRIANGLE_COUNT_U32, dstCount);
|
||||
|
||||
const dstIndices = collection._triangleIndexView;
|
||||
for (let i = 0; i < dstCount; i++) {
|
||||
dstIndices[(triangleOffset + i) * 3] = indices[i * 3];
|
||||
dstIndices[(triangleOffset + i) * 3 + 1] = indices[i * 3 + 1];
|
||||
dstIndices[(triangleOffset + i) * 3 + 2] = indices[i * 3 + 2];
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the polygon. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
* @override
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
positions: Array.from(this.getPositions()),
|
||||
holes: Array.from(this.getHoles()),
|
||||
triangles: Array.from(this.getTriangles()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPolygon;
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
// @ts-check
|
||||
|
||||
import defined from "../Core/defined.js";
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
import BufferPolygon from "./BufferPolygon.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import assert from "../Core/assert.js";
|
||||
import IndexDatatype from "../Core/IndexDatatype.js";
|
||||
import renderPolygons from "./renderBufferPolygonCollection.js";
|
||||
import BufferPolygonMaterial from "./BufferPolygonMaterial.js";
|
||||
|
||||
/** @import BlendOption from "./BlendOption.js"; */
|
||||
/** @import BoundingSphere from "../Core/BoundingSphere.js"; */
|
||||
/** @import { TypedArray } from "../Core/globalTypes.js"; */
|
||||
/** @import Matrix4 from "../Core/Matrix4.js"; */
|
||||
/** @import FrameState from "./FrameState.js" */
|
||||
/** @import ComponentDatatype from "../Core/ComponentDatatype.js"; */
|
||||
|
||||
const { ERR_CAPACITY } = BufferPrimitiveCollection.Error;
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPolygonOptions
|
||||
* @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @property {boolean} [show=true]
|
||||
* @property {BufferPolygonMaterial} [material=BufferPolygonMaterial.DEFAULT_MATERIAL]
|
||||
* @property {number} [featureId]
|
||||
* @property {object} [pickObject]
|
||||
* @property {TypedArray} [positions]
|
||||
* @property {TypedArray} [holes]
|
||||
* @property {TypedArray} [triangles]
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of polygons held in ArrayBuffer storage for performance and memory optimization.
|
||||
*
|
||||
* <p>Default buffer memory allocation is arbitrary, and collections cannot be resized,
|
||||
* so specific per-buffer capacities should be provided in the collection
|
||||
* constructor when available.</p>
|
||||
*
|
||||
* @example
|
||||
* import earcut from "earcut";
|
||||
*
|
||||
* const collection = new BufferPolygonCollection({
|
||||
* primitiveCountMax: 1024,
|
||||
* vertexCountMax: 4096,
|
||||
* holeCountMax: 1024,
|
||||
* triangleCountMax: 2048,
|
||||
* });
|
||||
*
|
||||
* const polygon = new BufferPolygon();
|
||||
* const positions = [ ... ];
|
||||
* const holes = [ ... ];
|
||||
* const material = new BufferPolygonMaterial({color: Color.WHITE});
|
||||
*
|
||||
* // Create a new polygon, temporarily bound to 'polygon' local variable.
|
||||
* collection.add({
|
||||
* positions: new Float64Array(positions),
|
||||
* holes: new Uint32Array(holes),
|
||||
* triangles: new Uint32Array(earcut(positions, holes, 3)),
|
||||
* material
|
||||
* }, polygon);
|
||||
*
|
||||
* // Iterate over all polygons in collection, temporarily binding 'polygon'
|
||||
* // local variable to each, and updating polygon material.
|
||||
* for (let i = 0; i < collection.primitiveCount; i++) {
|
||||
* collection.get(i, polygon);
|
||||
* polygon.setMaterial(material);
|
||||
* }
|
||||
*
|
||||
* @see BufferPolygon
|
||||
* @see BufferPolygonMaterial
|
||||
* @see BufferPrimitiveCollection
|
||||
* @extends BufferPrimitiveCollection<BufferPolygon>
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPolygonCollection extends BufferPrimitiveCollection {
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {number} [options.primitiveCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {number} [options.vertexCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {number} [options.holeCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {number} [options.triangleCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {ComponentDatatype} [options.positionDatatype=ComponentDatatype.DOUBLE]
|
||||
* @param {boolean} [options.positionNormalized=false]
|
||||
* @param {boolean} [options.show=true]
|
||||
* @param {boolean} [options.allowPicking=true] When <code>true</code>, primitives are pickable with {@link Scene#pick}. When <code>false</code>, memory and initialization cost are lower.
|
||||
* @param {BoundingSphere} [options.boundingVolume] Bounding volume, in world space, for the collection. When
|
||||
* unspecified, a bounding volume is computed automatically and updated when primitive positions change. When
|
||||
* specified, users are responsible for updating bounding volume as needed. Pre-computing the bounding volume
|
||||
* manually, and updating it only as needed, will improve performance for larger dynamic collections.
|
||||
* @param {boolean} [options.debugShowBoundingVolume=false]
|
||||
* @param {BlendOption} [options.blendOption=BlendOption.TRANSLUCENT]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
super(options);
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
this._holeCount = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this._holeCountMax =
|
||||
options.holeCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
|
||||
|
||||
/**
|
||||
* @type {TypedArray}
|
||||
* @ignore
|
||||
*/
|
||||
this._holeIndexView = null;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
this._triangleCount = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this._triangleCountMax =
|
||||
options.triangleCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
|
||||
|
||||
/**
|
||||
* @type {TypedArray}
|
||||
* @ignore
|
||||
*/
|
||||
this._triangleIndexView = null;
|
||||
|
||||
this._allocateHoleIndexBuffer();
|
||||
this._allocateTriangleIndexBuffer();
|
||||
}
|
||||
|
||||
_getCollectionClass() {
|
||||
return BufferPolygonCollection;
|
||||
}
|
||||
|
||||
_getPrimitiveClass() {
|
||||
return BufferPolygon;
|
||||
}
|
||||
|
||||
_getMaterialClass() {
|
||||
return BufferPolygonMaterial;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// COLLECTION LIFECYCLE
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_allocateHoleIndexBuffer() {
|
||||
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
|
||||
this._holeIndexView = IndexDatatype.createTypedArray(
|
||||
this._positionCountMax,
|
||||
this._holeCountMax,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_allocateTriangleIndexBuffer() {
|
||||
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
|
||||
this._triangleIndexView = IndexDatatype.createTypedArray(
|
||||
this._positionCountMax,
|
||||
this._triangleCountMax * 3,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates the contents of this collection into the result collection.
|
||||
* Result collection is not resized, and must contain enough space for all
|
||||
* primitives in the source collection. Existing polygons in the result
|
||||
* collection will be overwritten.
|
||||
*
|
||||
* <p>Useful when allocating more space for a collection that has reached its
|
||||
* capacity, and efficiently transferring polygons to the new collection.</p>
|
||||
*
|
||||
* @example
|
||||
* const result = new BufferPolygonCollection({ ... }); // allocate larger 'result' collection
|
||||
* BufferPolygonCollection.clone(collection, result); // copy polygons from 'collection' into 'result'
|
||||
*
|
||||
* @param {BufferPolygonCollection} collection
|
||||
* @param {BufferPolygonCollection} result
|
||||
* @returns {BufferPolygonCollection}
|
||||
*/
|
||||
static clone(collection, result) {
|
||||
super.clone(collection, result);
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(collection.holeCount <= result.holeCountMax, ERR_CAPACITY);
|
||||
assert(collection.triangleCount <= result.triangleCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._copySubArray(
|
||||
collection._holeIndexView,
|
||||
result._holeIndexView,
|
||||
collection.holeCount,
|
||||
);
|
||||
|
||||
this._copySubArray(
|
||||
collection._triangleIndexView,
|
||||
result._triangleIndexView,
|
||||
collection._triangleCount * 3,
|
||||
);
|
||||
|
||||
result._holeCount = collection._holeCount;
|
||||
result._triangleCount = collection._triangleCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferPolygonCollection} collection
|
||||
* @returns {BufferPolygonCollection}
|
||||
* @override
|
||||
* @ignore
|
||||
*/
|
||||
static _cloneEmpty(collection) {
|
||||
return new BufferPolygonCollection({
|
||||
primitiveCountMax: collection.primitiveCountMax,
|
||||
vertexCountMax: collection.vertexCountMax,
|
||||
holeCountMax: collection.holeCountMax,
|
||||
triangleCountMax: collection.triangleCountMax,
|
||||
positionDatatype: collection.positionDatatype,
|
||||
positionNormalized: collection.positionNormalized,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferPolygonCollection} src
|
||||
* @param {BufferPolygonCollection} dst
|
||||
* @override
|
||||
* @ignore
|
||||
*/
|
||||
static _replaceBuffers(src, dst) {
|
||||
super._replaceBuffers(src, dst);
|
||||
dst._holeIndexView = src._holeIndexView;
|
||||
dst._triangleIndexView = src._triangleIndexView;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// PRIMITIVE LIFECYCLE
|
||||
|
||||
/**
|
||||
* Adds a new polygon to the collection, with the specified options. A
|
||||
* {@link BufferPolygon} instance is linked to the new polygon, using
|
||||
* the 'result' argument if given, or a new instance if not. For repeated
|
||||
* calls, prefer to reuse a single BufferPolygon instance rather than
|
||||
* allocating a new instance on each call.
|
||||
*
|
||||
* @param {BufferPolygonOptions} options
|
||||
* @param {BufferPolygon} result
|
||||
* @returns {BufferPolygon}
|
||||
* @override
|
||||
*/
|
||||
add(options, result = new BufferPolygon()) {
|
||||
super.add(options, result);
|
||||
|
||||
const vertexOffset = this._positionCount;
|
||||
result._setUint32(BufferPolygon.Layout.POSITION_OFFSET_U32, vertexOffset);
|
||||
result._setUint32(BufferPolygon.Layout.POSITION_COUNT_U32, 0);
|
||||
|
||||
const holeOffset = this._holeCount;
|
||||
result._setUint32(BufferPolygon.Layout.HOLE_OFFSET_U32, holeOffset);
|
||||
result._setUint32(BufferPolygon.Layout.HOLE_COUNT_U32, 0);
|
||||
|
||||
const triangleOffset = this._triangleCount;
|
||||
result._setUint32(BufferPolygon.Layout.TRIANGLE_OFFSET_U32, triangleOffset);
|
||||
result._setUint32(BufferPolygon.Layout.TRIANGLE_COUNT_U32, 0);
|
||||
|
||||
if (defined(options.positions)) {
|
||||
result.setPositions(options.positions);
|
||||
}
|
||||
|
||||
if (defined(options.holes)) {
|
||||
result.setHoles(options.holes);
|
||||
}
|
||||
|
||||
if (defined(options.triangles)) {
|
||||
result.setTriangles(options.triangles);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// RENDER
|
||||
|
||||
/**
|
||||
* @param {FrameState} frameState
|
||||
* @ignore
|
||||
*/
|
||||
update(frameState) {
|
||||
super.update(frameState);
|
||||
|
||||
const passes = frameState.passes;
|
||||
if (this.show && (passes.render || passes.pick)) {
|
||||
this._renderContext = renderPolygons(
|
||||
this,
|
||||
frameState,
|
||||
this._renderContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// ACCESSORS
|
||||
|
||||
/**
|
||||
* Total byte length of buffers owned by this collection. Includes any unused
|
||||
* space allocated by {@link primitiveCountMax}, even if no polygons have
|
||||
* yet been added in that space.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @override
|
||||
*/
|
||||
get byteLength() {
|
||||
return (
|
||||
super.byteLength +
|
||||
this._holeIndexView.byteLength +
|
||||
this._triangleIndexView.byteLength
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of holes in collection. Must be <= {@link holeCountMax}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get holeCount() {
|
||||
return this._holeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of holes in collection. Must be >= {@link holeCount}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY}
|
||||
*/
|
||||
get holeCountMax() {
|
||||
return this._holeCountMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of triangles in collection. Must be <= {@link triangleCountMax}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get triangleCount() {
|
||||
return this._triangleCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of triangles in collection. Must be >= {@link triangleCount}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY}
|
||||
*/
|
||||
get triangleCountMax() {
|
||||
return this._triangleCountMax;
|
||||
}
|
||||
}
|
||||
export default BufferPolygonCollection;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// @ts-check
|
||||
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js";
|
||||
|
||||
/** @import Color from "../Core/Color.js"; */
|
||||
/** @import BufferPolygon from "./BufferPolygon.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPolygonMaterialOptions
|
||||
* @property {Color} [color=Color.WHITE] Color of fill.
|
||||
* @property {Color} [outlineColor=Color.WHITE] Color of outline.
|
||||
* @property {number} [outlineWidth=0.0] Width of outline, 0-255px.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Material description for a {@link BufferPolygon}.
|
||||
*
|
||||
* <p>BufferPolygonMaterial objects are {@link Packable|packable}, stored
|
||||
* when calling {@link BufferPolygon#setMaterial}. Subsequent changes to the
|
||||
* material will not affect the polygon until setMaterial() is called again.</p>
|
||||
*
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
* @extends BufferPrimitiveMaterial
|
||||
*/
|
||||
class BufferPolygonMaterial extends BufferPrimitiveMaterial {
|
||||
/**
|
||||
* @type {BufferPolygonMaterial}
|
||||
* @ignore
|
||||
*/
|
||||
static DEFAULT_MATERIAL = Object.freeze(new BufferPolygonMaterial());
|
||||
|
||||
/**
|
||||
* @param {BufferPolygonMaterialOptions} [options]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
super(options);
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPolygonMaterial;
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// @ts-check
|
||||
|
||||
import BufferPrimitive from "./BufferPrimitive.js";
|
||||
import assert from "../Core/assert.js";
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/** @import { TypedArray, TypedArrayConstructor } from "../Core/globalTypes.js"; */
|
||||
/** @import BufferPolylineCollection from "./BufferPolylineCollection.js"; */
|
||||
|
||||
const { ERR_RESIZE, ERR_CAPACITY } = BufferPrimitiveCollection.Error;
|
||||
|
||||
/**
|
||||
* View bound to the underlying buffer data of a {@link BufferPolylineCollection}.
|
||||
*
|
||||
* <p>BufferPolyline instances are {@link https://en.wikipedia.org/wiki/Flyweight_pattern|flyweights}:
|
||||
* a single BufferPolyline instance can be temporarily bound to any conceptual
|
||||
* "polyline" in a BufferPolylineCollection, allowing very large collections to be
|
||||
* iterated and updated with a minimal memory footprint.</p>
|
||||
*
|
||||
* Represented as two (2) or more positions.
|
||||
*
|
||||
* @see BufferPolylineCollection
|
||||
* @see BufferPolylineMaterial
|
||||
* @see BufferPrimitive
|
||||
* @extends BufferPrimitive
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPolyline extends BufferPrimitive {
|
||||
/**
|
||||
* @type {BufferPolylineCollection}
|
||||
* @ignore
|
||||
*/
|
||||
_collection = null;
|
||||
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
...BufferPrimitive.Layout,
|
||||
|
||||
/**
|
||||
* Offset in position array to first vertex in polyline, number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
POSITION_OFFSET_U32: BufferPrimitive.Layout.__BYTE_LENGTH,
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this polyline, number of VEC3 elements.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
POSITION_COUNT_U32: BufferPrimitive.Layout.__BYTE_LENGTH + 4,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
__BYTE_LENGTH: BufferPrimitive.Layout.__BYTE_LENGTH + 8,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// LIFECYCLE
|
||||
|
||||
/**
|
||||
* Copies data from source polyline to result. If the result polyline is not
|
||||
* new (the last polyline in the collection) then source and result polylines
|
||||
* must have the same vertex counts.
|
||||
*
|
||||
* @param {BufferPolyline} polyline
|
||||
* @param {BufferPolyline} result
|
||||
* @return {BufferPolyline}
|
||||
* @override
|
||||
*/
|
||||
static clone(polyline, result) {
|
||||
super.clone(polyline, result);
|
||||
result.setPositions(polyline.getPositions());
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// GEOMETRY
|
||||
|
||||
/**
|
||||
* Offset in collection position array to first vertex in polyline, number
|
||||
* of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
get vertexOffset() {
|
||||
return this._getUint32(BufferPolyline.Layout.POSITION_OFFSET_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of positions (vertices) in this polyline, number of VEC3 elements.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get vertexCount() {
|
||||
return this._getUint32(BufferPolyline.Layout.POSITION_COUNT_U32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array view of this polyline's vertex positions. If 'result'
|
||||
* argument is given, vertex positions are written to that array and returned.
|
||||
* Otherwise, returns an ArrayView on collection memory — changes to this array
|
||||
* will not trigger render updates, which requires `.setPositions()`.
|
||||
*
|
||||
* @param {TypedArray} [result]
|
||||
* return {TypedArray}
|
||||
*/
|
||||
getPositions(result) {
|
||||
const { vertexOffset, vertexCount } = this;
|
||||
const positionView = this._collection._positionView;
|
||||
|
||||
if (!defined(result)) {
|
||||
const byteOffset =
|
||||
positionView.byteOffset +
|
||||
vertexOffset * 3 * positionView.BYTES_PER_ELEMENT;
|
||||
const TypedArray = /** @type {TypedArrayConstructor} */ (
|
||||
positionView.constructor
|
||||
);
|
||||
return new TypedArray(
|
||||
/** @type {ArrayBuffer} */ (positionView.buffer),
|
||||
byteOffset,
|
||||
vertexCount * 3,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < vertexCount; i++) {
|
||||
result[i * 3] = positionView[(vertexOffset + i) * 3];
|
||||
result[i * 3 + 1] = positionView[(vertexOffset + i) * 3 + 1];
|
||||
result[i * 3 + 2] = positionView[(vertexOffset + i) * 3 + 2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @param {TypedArray} positions */
|
||||
setPositions(positions) {
|
||||
const collection = this._collection;
|
||||
const vertexOffset = this.vertexOffset;
|
||||
const srcCount = this.vertexCount;
|
||||
const dstCount = positions.length / 3;
|
||||
const collectionCount = collection._positionCount + dstCount - srcCount;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
assert(srcCount === dstCount || this._isResizable(), ERR_RESIZE);
|
||||
assert(collectionCount <= collection.vertexCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
collection._positionCount = collectionCount;
|
||||
this._setUint32(BufferPolyline.Layout.POSITION_COUNT_U32, dstCount);
|
||||
|
||||
const positionView = collection._positionView;
|
||||
for (let i = 0; i < dstCount; i++) {
|
||||
positionView[(vertexOffset + i) * 3] = positions[i * 3];
|
||||
positionView[(vertexOffset + i) * 3 + 1] = positions[i * 3 + 1];
|
||||
positionView[(vertexOffset + i) * 3 + 2] = positions[i * 3 + 2];
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
collection._makeDirtyBoundingVolume();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the polyline. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
* @override
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
positions: Array.from(this.getPositions()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPolyline;
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
// @ts-check
|
||||
|
||||
import defined from "../Core/defined.js";
|
||||
import BufferPrimitiveCollection from "./BufferPrimitiveCollection.js";
|
||||
import BufferPolyline from "./BufferPolyline.js";
|
||||
import renderPolylines from "./renderBufferPolylineCollection.js";
|
||||
import BufferPolylineMaterial from "./BufferPolylineMaterial.js";
|
||||
|
||||
/** @import { TypedArray } from "../Core/globalTypes.js"; */
|
||||
/** @import Matrix4 from "../Core/Matrix4.js"; */
|
||||
/** @import FrameState from "./FrameState.js" */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPolylineOptions
|
||||
* @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @property {boolean} [show=true]
|
||||
* @property {BufferPolylineMaterial} [material=BufferPolylineMaterial.DEFAULT_MATERIAL]
|
||||
* @property {number} [featureId]
|
||||
* @property {object} [pickObject]
|
||||
* @property {TypedArray} [positions]
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of polylines held in ArrayBuffer storage for performance and memory optimization.
|
||||
*
|
||||
* <p>Default buffer memory allocation is arbitrary, and collections cannot be resized,
|
||||
* so specific per-buffer capacities should be provided in the collection
|
||||
* constructor when available.</p>
|
||||
*
|
||||
* @example
|
||||
* const collection = new BufferPolylineCollection({
|
||||
* primitiveCountMax: 1024,
|
||||
* vertexCountMax: 4096,
|
||||
* });
|
||||
*
|
||||
* const polyline = new BufferPolyline();
|
||||
* const material = new BufferPolylineMaterial({color: Color.WHITE});
|
||||
*
|
||||
* // Create a new polyline, temporarily bound to 'polyline' local variable.
|
||||
* collection.add({
|
||||
* positions: new Float64Array([ ... ]),
|
||||
* material,
|
||||
* }, polyline);
|
||||
*
|
||||
* // Iterate over all polylines in collection, temporarily binding 'polyline'
|
||||
* // local variable to each, and updating polyline material.
|
||||
* for (let i = 0; i < collection.primitiveCount; i++) {
|
||||
* collection.get(i, polyline);
|
||||
* polyline.setMaterial(material);
|
||||
* }
|
||||
*
|
||||
* @see BufferPolyline
|
||||
* @see BufferPolylineMaterial
|
||||
* @see BufferPrimitiveCollection
|
||||
* @extends BufferPrimitiveCollection<BufferPolyline>
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPolylineCollection extends BufferPrimitiveCollection {
|
||||
_getCollectionClass() {
|
||||
return BufferPolylineCollection;
|
||||
}
|
||||
|
||||
_getPrimitiveClass() {
|
||||
return BufferPolyline;
|
||||
}
|
||||
|
||||
_getMaterialClass() {
|
||||
return BufferPolylineMaterial;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// COLLECTION LIFECYCLE
|
||||
|
||||
/**
|
||||
* @param {BufferPolylineCollection} collection
|
||||
* @returns {BufferPolylineCollection}
|
||||
* @override
|
||||
* @ignore
|
||||
*/
|
||||
static _cloneEmpty(collection) {
|
||||
return new BufferPolylineCollection({
|
||||
primitiveCountMax: collection.primitiveCountMax,
|
||||
vertexCountMax: collection.vertexCountMax,
|
||||
positionDatatype: collection.positionDatatype,
|
||||
positionNormalized: collection.positionNormalized,
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// PRIMITIVE LIFECYCLE
|
||||
|
||||
/**
|
||||
* Adds a new polyline to the collection, with the specified options. A
|
||||
* {@link BufferPolyline} instance is linked to the new polyline, using
|
||||
* the 'result' argument if given, or a new instance if not. For repeated
|
||||
* calls, prefer to reuse a single BufferPolyline instance rather than
|
||||
* allocating a new instance on each call.
|
||||
*
|
||||
* @param {BufferPolylineOptions} options
|
||||
* @param {BufferPolyline} result
|
||||
* @returns {BufferPolyline}
|
||||
* @override
|
||||
*/
|
||||
add(options, result = new BufferPolyline()) {
|
||||
super.add(options, result);
|
||||
|
||||
const vertexOffset = this._positionCount;
|
||||
result._setUint32(BufferPolyline.Layout.POSITION_OFFSET_U32, vertexOffset);
|
||||
result._setUint32(BufferPolyline.Layout.POSITION_COUNT_U32, 0);
|
||||
|
||||
if (defined(options.positions)) {
|
||||
result.setPositions(options.positions);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// RENDER
|
||||
|
||||
/**
|
||||
* @param {FrameState} frameState
|
||||
* @ignore
|
||||
*/
|
||||
update(frameState) {
|
||||
super.update(frameState);
|
||||
|
||||
const passes = frameState.passes;
|
||||
if (this.show && (passes.render || passes.pick)) {
|
||||
this._renderContext = renderPolylines(
|
||||
this,
|
||||
frameState,
|
||||
this._renderContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPolylineCollection;
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// @ts-check
|
||||
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js";
|
||||
|
||||
/** @import Color from "../Core/Color.js"; */
|
||||
/** @import BufferPolyline from "./BufferPolyline.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPolylineMaterialOptions
|
||||
* @property {Color} [color=Color.WHITE] Color of fill.
|
||||
* @property {Color} [outlineColor=Color.WHITE] Color of outline.
|
||||
* @property {number} [outlineWidth=0.0] Width of outline, 0-255px.
|
||||
* @property {number} [width=1.0] Width of line, 0-255px.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Material description for a {@link BufferPolyline}.
|
||||
*
|
||||
* <p>BufferPolylineMaterial objects are {@link Packable|packable}, stored
|
||||
* when calling {@link BufferPolyline#setMaterial}. Subsequent changes to the
|
||||
* material will not affect the polyline until setMaterial() is called again.</p>
|
||||
*
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
* @extends BufferPrimitiveMaterial
|
||||
*/
|
||||
class BufferPolylineMaterial extends BufferPrimitiveMaterial {
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
...BufferPrimitiveMaterial.Layout,
|
||||
WIDTH_U8: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH,
|
||||
__BYTE_LENGTH: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {BufferPolylineMaterial}
|
||||
* @ignore
|
||||
*/
|
||||
static DEFAULT_MATERIAL = Object.freeze(new BufferPolylineMaterial());
|
||||
|
||||
/**
|
||||
* @param {BufferPolylineMaterialOptions} [options]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
super(options);
|
||||
|
||||
/**
|
||||
* Width of polyline, 0–255px.
|
||||
* @type {number}
|
||||
*/
|
||||
this.width = options.width ?? 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferPolylineMaterial} material
|
||||
* @param {DataView} view
|
||||
* @param {number} byteOffset
|
||||
* @override
|
||||
*/
|
||||
static pack(material, view, byteOffset) {
|
||||
super.pack(material, view, byteOffset);
|
||||
view.setUint8(this.Layout.WIDTH_U8 + byteOffset, material.width);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DataView} view
|
||||
* @param {number} byteOffset
|
||||
* @param {BufferPolylineMaterial} result
|
||||
* @returns {BufferPolylineMaterial}
|
||||
* @override
|
||||
*/
|
||||
static unpack(view, byteOffset, result) {
|
||||
super.unpack(view, byteOffset, result);
|
||||
result.width = view.getUint8(this.Layout.WIDTH_U8 + byteOffset);
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the material. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
*/
|
||||
toJSON() {
|
||||
return { ...super.toJSON(), width: this.width };
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPolylineMaterial;
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
// @ts-check
|
||||
|
||||
import assert from "../Core/assert.js";
|
||||
|
||||
/** @import BufferPrimitiveCollection from './BufferPrimitiveCollection.js'; */
|
||||
/** @import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js"; */
|
||||
|
||||
/**
|
||||
* View bound to the underlying buffer data of a {@link BufferPrimitiveCollection}. Abstract.
|
||||
*
|
||||
* <p>BufferPrimitive instances are intended to be reused when iterating over large collections,
|
||||
* and temporarily bound to a primitive index while performing read/write operations on that primitive,
|
||||
* before being rebound to the next primitive, using the
|
||||
* {@link https://en.wikipedia.org/wiki/Flyweight_pattern|flyweight pattern}.</p>
|
||||
*
|
||||
* @see BufferPrimitiveCollection
|
||||
* @see BufferPrimitiveMaterial
|
||||
* @see BufferPoint
|
||||
* @see BufferPolyline
|
||||
* @see BufferPolygon
|
||||
*
|
||||
* @abstract
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPrimitive {
|
||||
/**
|
||||
* Collecton containing the primitive(s) for which this instance currently
|
||||
* provides a view.
|
||||
*
|
||||
* @type {BufferPrimitiveCollection<BufferPrimitive>}
|
||||
* @ignore
|
||||
*/
|
||||
_collection = null;
|
||||
|
||||
/**
|
||||
* Index of the primitive for which this instance currently provides a view.
|
||||
*
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
_index = -1;
|
||||
|
||||
/**
|
||||
* Byte offset, into the collection's primitive buffer, of the primitive for
|
||||
* which this instance currently provides a view.
|
||||
*
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
_byteOffset = -1;
|
||||
|
||||
/**
|
||||
* Binary layout for this primitive type in collection's primitive buffer.
|
||||
* Each `Layout.MY_KEY_U32` entry is an offset in bytes, relative to the
|
||||
* start offset of the primitive, with the suffix indicating the data type
|
||||
* stored at that offset.
|
||||
*
|
||||
* The final entry, `__BYTE_LENGTH`, is not a pointer into a buffer — its
|
||||
* literal value is the total byte length of one primitive in the primitive
|
||||
* buffer, exclusive of other buffers.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
static Layout = {
|
||||
/**
|
||||
* Feature ID associated with the primitive; not required to be unique.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
FEATURE_ID_U32: 0,
|
||||
|
||||
/**
|
||||
* Boolean (0 or 1) flag indicating whether primitive is shown.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
SHOW_U8: 4,
|
||||
|
||||
/**
|
||||
* Boolean (0 or 1) flag indicating whether primitive is dirty.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
DIRTY_U8: 5,
|
||||
|
||||
/**
|
||||
* Pick ID (uint32) of primitive.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
PICK_ID_U32: 8,
|
||||
|
||||
/**
|
||||
* Byte length of one primitive in the primitive buffer, exclusive of
|
||||
* other buffers. Literal value, not a pointer.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
__BYTE_LENGTH: 12,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// LIFECYCLE
|
||||
|
||||
/**
|
||||
* Copies data from source primitive to result. If the result primitive is not
|
||||
* new (the last primitive in the collection) then source and result primitives
|
||||
* must have the same vertex counts.
|
||||
*
|
||||
* @param {BufferPrimitive} primitive
|
||||
* @param {BufferPrimitive} result
|
||||
* @returns {BufferPrimitive}
|
||||
*/
|
||||
static clone(primitive, result) {
|
||||
const SrcMaterialClass = primitive._collection._getMaterialClass();
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
const DstMaterialClass = result._collection._getMaterialClass();
|
||||
assert(SrcMaterialClass === DstMaterialClass, "Incompatible materials");
|
||||
//>>includeEnd('debug');
|
||||
|
||||
result.featureId = primitive.featureId;
|
||||
result.show = primitive.show;
|
||||
result.setMaterial(primitive.getMaterial(new SrcMaterialClass()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this primitive's memory footprint is resizable. Only the
|
||||
* newest (most recently created) primitive in a collection can be resized,
|
||||
* to guarantee fast and stable performance.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
_isResizable() {
|
||||
return this._index === this._collection.primitiveCount - 1;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// ACCESSORS
|
||||
|
||||
/**
|
||||
* Feature ID associated with the primitive; not required to be unique.
|
||||
* @type {number}
|
||||
*/
|
||||
get featureId() {
|
||||
return this._getUint32(BufferPrimitive.Layout.FEATURE_ID_U32);
|
||||
}
|
||||
|
||||
set featureId(featureId) {
|
||||
this._setUint32(BufferPrimitive.Layout.FEATURE_ID_U32, featureId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether primitive is shown.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get show() {
|
||||
return this._getUint8(BufferPrimitive.Layout.SHOW_U8) === 1;
|
||||
}
|
||||
|
||||
set show(show) {
|
||||
this._setUint8(BufferPrimitive.Layout.SHOW_U8, show ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferPrimitiveMaterial} result
|
||||
* @returns {BufferPrimitiveMaterial}
|
||||
*/
|
||||
getMaterial(result) {
|
||||
const collection = this._collection;
|
||||
const MaterialClass = collection._getMaterialClass();
|
||||
|
||||
return MaterialClass.unpack(
|
||||
collection._materialView,
|
||||
this._index * MaterialClass.packedLength,
|
||||
result,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BufferPrimitiveMaterial} material
|
||||
*/
|
||||
setMaterial(material) {
|
||||
const collection = this._collection;
|
||||
const MaterialClass = collection._getMaterialClass();
|
||||
|
||||
MaterialClass.pack(
|
||||
material,
|
||||
collection._materialView,
|
||||
this._index * MaterialClass.packedLength,
|
||||
);
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the primitive requires an update on next render. Renderers should
|
||||
* _not_ iterate over all primitives each frame, but must instead inspect
|
||||
* only the dirty range of the parent collection. This flag is managed
|
||||
* automatically, by primitive setters and collection renderers.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @ignore
|
||||
*
|
||||
* @see BufferPrimitiveCollection#_dirtyOffset
|
||||
* @see BufferPrimitiveCollection#_dirtyCount
|
||||
*/
|
||||
get _dirty() {
|
||||
return this._getUint8(BufferPrimitive.Layout.DIRTY_U8) === 1;
|
||||
}
|
||||
|
||||
set _dirty(dirty) {
|
||||
// Avoid `._setUint8()` here, which would infinitely loop `._dirty = true`.
|
||||
this._collection._primitiveView.setUint8(
|
||||
this._byteOffset + BufferPrimitive.Layout.DIRTY_U8,
|
||||
dirty ? 1 : 0,
|
||||
);
|
||||
|
||||
// A 'dirty' primitive is responsible for notifying the collection. Applying
|
||||
// updates and marking the primitive 'clean' will be handled by the collection,
|
||||
// so we don't notify the collection here in that case.
|
||||
if (dirty) {
|
||||
this._collection._makeDirty(this._index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick ID (uint32) of primitive.
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
get _pickId() {
|
||||
return this._getUint32(BufferPrimitive.Layout.PICK_ID_U32);
|
||||
}
|
||||
|
||||
set _pickId(pickId) {
|
||||
this._setUint32(BufferPrimitive.Layout.PICK_ID_U32, pickId);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// BUFFER ACCESSORS
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @returns {number}
|
||||
* @ignore
|
||||
*/
|
||||
_getUint8(itemByteOffset) {
|
||||
return this._collection._primitiveView.getUint8(
|
||||
this._byteOffset + itemByteOffset,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @param {number} itemValue
|
||||
* @ignore
|
||||
*/
|
||||
_setUint8(itemByteOffset, itemValue) {
|
||||
this._collection._primitiveView.setUint8(
|
||||
this._byteOffset + itemByteOffset,
|
||||
itemValue,
|
||||
);
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @returns {number}
|
||||
* @ignore
|
||||
*/
|
||||
_getUint32(itemByteOffset) {
|
||||
return this._collection._primitiveView.getUint32(
|
||||
this._byteOffset + itemByteOffset,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @param {number} itemValue
|
||||
* @ignore
|
||||
*/
|
||||
_setUint32(itemByteOffset, itemValue) {
|
||||
this._collection._primitiveView.setUint32(
|
||||
this._byteOffset + itemByteOffset,
|
||||
itemValue,
|
||||
true,
|
||||
);
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @returns {number}
|
||||
* @ignore
|
||||
*/
|
||||
_getFloat32(itemByteOffset) {
|
||||
return this._collection._primitiveView.getFloat32(
|
||||
this._byteOffset + itemByteOffset,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemByteOffset
|
||||
* @param {number} itemValue
|
||||
* @ignore
|
||||
*/
|
||||
_setFloat32(itemByteOffset, itemValue) {
|
||||
this._collection._primitiveView.setFloat32(
|
||||
this._byteOffset + itemByteOffset,
|
||||
itemValue,
|
||||
true,
|
||||
);
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the primitive. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
*/
|
||||
toJSON() {
|
||||
const collection = this._collection;
|
||||
const MaterialClass = collection._getMaterialClass();
|
||||
|
||||
return {
|
||||
featureId: this.featureId,
|
||||
show: this.show,
|
||||
dirty: this._dirty,
|
||||
material: this.getMaterial(new MaterialClass()).toJSON(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPrimitive;
|
||||
+869
@@ -0,0 +1,869 @@
|
||||
// @ts-check
|
||||
|
||||
import BoundingSphere from "../Core/BoundingSphere.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import assert from "../Core/assert.js";
|
||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import AttributeCompression from "../Core/AttributeCompression.js";
|
||||
import SceneMode from "./SceneMode.js";
|
||||
import AttributeType from "./AttributeType.js";
|
||||
import oneTimeWarning from "../Core/oneTimeWarning.js";
|
||||
import BlendOption from "../Scene/BlendOption.js";
|
||||
|
||||
/** @import { Destroyable, TypedArray, TypedArrayConstructor } from "../Core/globalTypes.js"; */
|
||||
/** @import Context from "../Renderer/Context.js"; */
|
||||
/** @import FrameState from "./FrameState.js"; */
|
||||
/** @import BufferPrimitive from "./BufferPrimitive.js"; */
|
||||
/** @import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js"; */
|
||||
/** @import PickId from "../Renderer/PickId.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPrimitiveOptions
|
||||
* @property {Matrix4} [modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @property {boolean} [show=true]
|
||||
* @property {BufferPrimitiveMaterial} [material]
|
||||
* @property {number} [featureId]
|
||||
* @property {object} [pickObject]
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of primitives held in ArrayBuffer storage for performance and memory optimization.
|
||||
*
|
||||
* <p>To get the full performance benefit of using a BufferPrimitiveCollection containing "N" primitives,
|
||||
* be careful to avoid allocating "N" instances of any related JavaScript object. {@link BufferPrimitive},
|
||||
* {@link Color}, {@link Cartesian3}, and other objects can all be reused when working with large collections,
|
||||
* using the {@link https://en.wikipedia.org/wiki/Flyweight_pattern|flyweight pattern}.</p>
|
||||
*
|
||||
* @abstract
|
||||
* @template T extends BufferPrimitive
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*
|
||||
* @see BufferPrimitive
|
||||
* @see BufferPrimitiveMaterial
|
||||
* @see BufferPointCollection
|
||||
* @see BufferPolylineCollection
|
||||
* @see BufferPolygonCollection
|
||||
*/
|
||||
class BufferPrimitiveCollection {
|
||||
/** @ignore */
|
||||
static Error = {
|
||||
ERR_RESIZE: "BufferPrimitive range cannot be resized after initialization.",
|
||||
ERR_CAPACITY: "BufferPrimitiveCollection capacity exceeded.",
|
||||
ERR_MULTIPLE_OF_FOUR:
|
||||
"BufferPrimitive byte length must be a multiple of 4.",
|
||||
ERR_OUT_OF_RANGE: "BufferPrimitive buffer access out of range.",
|
||||
};
|
||||
|
||||
/**
|
||||
* Resources managed by the collection's renderer. Collections may have multiple renderer
|
||||
* implementations, so the collection should be ignorant of the renderer's implementation
|
||||
* and context data. A collection only has one renderer active at a time.
|
||||
*
|
||||
* @type {Destroyable|null}
|
||||
* @ignore
|
||||
*/
|
||||
_renderContext = null;
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] Transforms geometry from model to world coordinates.
|
||||
* @param {number} [options.primitiveCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {number} [options.vertexCountMax=BufferPrimitiveCollection.DEFAULT_CAPACITY]
|
||||
* @param {boolean} [options.show=true]
|
||||
* @param {ComponentDatatype} [options.positionDatatype=ComponentDatatype.DOUBLE]
|
||||
* @param {boolean} [options.positionNormalized=false] When <code>true</code>, integer position values are treated as normalized,
|
||||
* where the full integer range maps to [-1, 1] (signed) or [0, 1] (unsigned). Only relevant for integer position datatypes
|
||||
* (BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT).
|
||||
* @param {boolean} [options.allowPicking=false] When <code>true</code>, primitives are pickable with {@link Scene#pick}. When <code>false</code>, memory and initialization cost are lower.
|
||||
* @param {BoundingSphere} [options.boundingVolume] Bounding volume, in world space, for the collection. When
|
||||
* unspecified, a bounding volume is computed automatically and updated when primitive positions change. When
|
||||
* specified, users are responsible for updating bounding volume as needed. Pre-computing the bounding volume
|
||||
* manually, and updating it only as needed, will improve performance for larger dynamic collections.
|
||||
* @param {boolean} [options.debugShowBoundingVolume=false]
|
||||
* @param {BlendOption} [options.blendOption=BlendOption.TRANSLUCENT]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
/**
|
||||
* Determines if primitives in this collection will be shown.
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.show = options.show ?? true;
|
||||
|
||||
/**
|
||||
* Collection blend option; must be OPAQUE or TRANSLUCENT.
|
||||
* @type {BlendOption}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
this._blendOption = options.blendOption ?? BlendOption.TRANSLUCENT;
|
||||
|
||||
/**
|
||||
* Transforms geometry from model to world coordinates.
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
* @readonly
|
||||
* @protected
|
||||
*/
|
||||
this._modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
|
||||
|
||||
/**
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
* @protected
|
||||
*/
|
||||
this._boundingVolume = BoundingSphere.clone(
|
||||
options.boundingVolume ?? new BoundingSphere(),
|
||||
new BoundingSphere(),
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @protected
|
||||
*/
|
||||
this._boundingVolumeAutoUpdate = !defined(options.boundingVolume);
|
||||
|
||||
/**
|
||||
* When <code>true</code>, primitives are pickable with {@link Scene#pick}.
|
||||
* When <code>false</code>, memory and initialization cost are lower.
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @ignore
|
||||
* @default false
|
||||
*/
|
||||
this._allowPicking = options.allowPicking ?? false;
|
||||
|
||||
/**
|
||||
* @type {Map<Context, PickId[]>}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
this._pickIds = new Map();
|
||||
|
||||
/**
|
||||
* @type {object[]}
|
||||
* @readonly
|
||||
* @ignore
|
||||
*/
|
||||
this._pickObjects = [];
|
||||
|
||||
/**
|
||||
* This property is for debugging only; it is not for production use nor is it optimized.
|
||||
* <p>
|
||||
* Draws the bounding sphere for each draw command in the primitive.
|
||||
* </p>
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.debugShowBoundingVolume = options.debugShowBoundingVolume ?? false;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this._primitiveCount = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this._primitiveCountMax =
|
||||
options.primitiveCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
|
||||
|
||||
/**
|
||||
* @type {DataView<ArrayBuffer>}
|
||||
* @ignore
|
||||
*/
|
||||
this._primitiveView = null;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
this._positionCount = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this._positionCountMax =
|
||||
options.vertexCountMax ?? BufferPrimitiveCollection.DEFAULT_CAPACITY;
|
||||
|
||||
/**
|
||||
* @type {TypedArray}
|
||||
* @ignore
|
||||
*/
|
||||
this._positionView = null;
|
||||
|
||||
/**
|
||||
* @type {ComponentDatatype}
|
||||
* @ignore
|
||||
*/
|
||||
this._positionDatatype =
|
||||
options.positionDatatype ?? ComponentDatatype.DOUBLE;
|
||||
|
||||
/**
|
||||
* When <code>true</code>, integer position values represent normalized floats
|
||||
* in [-1, 1] (signed) or [0, 1] (unsigned). Only applicable to integer datatypes.
|
||||
* @type {boolean}
|
||||
* @ignore
|
||||
*/
|
||||
this._positionNormalized = options.positionNormalized ?? false;
|
||||
|
||||
/**
|
||||
* @type {DataView<ArrayBuffer>}
|
||||
* @ignore
|
||||
*/
|
||||
this._materialView = null;
|
||||
|
||||
// Potentially-dirty primitives are tracked as a contiguous range, with
|
||||
// 'clean' primitives potentially within the range. Individual primitive
|
||||
// 'dirty' flags are source-of-truth.
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
this._dirtyOffset = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @ignore
|
||||
*/
|
||||
this._dirtyCount = 0;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @ignore
|
||||
*/
|
||||
this._dirtyBoundingVolume = false;
|
||||
|
||||
this._allocatePrimitiveBuffer();
|
||||
this._allocatePositionBuffer();
|
||||
this._allocateMaterialBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessing `this.constructor` can cause JSDoc builds to fail, so use this
|
||||
* protected getter function instead.
|
||||
* @protected
|
||||
* @return {*}
|
||||
* @ignore
|
||||
*/
|
||||
_getCollectionClass() {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @return {*}
|
||||
* @ignore
|
||||
*/
|
||||
_getPrimitiveClass() {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {*}
|
||||
* @ignore
|
||||
*/
|
||||
_getMaterialClass() {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// COLLECTION LIFECYCLE
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_allocatePrimitiveBuffer() {
|
||||
const layout = this._getPrimitiveClass().Layout;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
const { ERR_MULTIPLE_OF_FOUR } = BufferPrimitiveCollection.Error;
|
||||
assert(layout.__BYTE_LENGTH % 4 === 0, ERR_MULTIPLE_OF_FOUR);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._primitiveView = new DataView(
|
||||
new ArrayBuffer(this._primitiveCountMax * layout.__BYTE_LENGTH),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_allocatePositionBuffer() {
|
||||
// @ts-expect-error https://github.com/CesiumGS/cesium/issues/13420
|
||||
this._positionView = ComponentDatatype.createTypedArray(
|
||||
this._positionDatatype,
|
||||
this._positionCountMax * 3,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_allocateMaterialBuffer() {
|
||||
const MaterialClass = this._getMaterialClass();
|
||||
this._materialView = new DataView(
|
||||
new ArrayBuffer(this._primitiveCountMax * MaterialClass.packedLength),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this object was destroyed; otherwise, false.
|
||||
*
|
||||
* @returns {boolean} True if this object was destroyed; otherwise, false.
|
||||
*/
|
||||
isDestroyed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Destroys collection and its GPU resources. */
|
||||
destroy() {
|
||||
this._pickObjects.length = 0;
|
||||
|
||||
for (const contextPickIds of this._pickIds.values()) {
|
||||
for (const pickId of contextPickIds) {
|
||||
pickId.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (defined(this._renderContext)) {
|
||||
this._renderContext.destroy();
|
||||
this._renderContext = undefined;
|
||||
this._dirtyOffset = 0;
|
||||
this._dirtyCount = this.primitiveCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts primitives of the collection.
|
||||
*
|
||||
* Because sorting changes the indices (but not the feature IDs) of primitives
|
||||
* in the collection, the function also returns an array mapping from previous
|
||||
* index to new index. When sorting repeatedly, the array can be reused and
|
||||
* passed as the 'result' argument for each call.
|
||||
*
|
||||
* @param {Function} sortFn
|
||||
* @param {Uint32Array} result
|
||||
* @returns {Uint32Array} Mapping from previous index to new index.
|
||||
*/
|
||||
sort(sortFn, result = new Uint32Array(this.primitiveCount)) {
|
||||
const PrimitiveClass = this._getPrimitiveClass();
|
||||
const CollectionClass = this._getCollectionClass();
|
||||
|
||||
const { primitiveCount } = this;
|
||||
|
||||
const a = new PrimitiveClass();
|
||||
const b = new PrimitiveClass();
|
||||
|
||||
// Mapping from NEW index to PREVIOUS index.
|
||||
const dstSrcMap = new Uint32Array(primitiveCount);
|
||||
for (let i = 0; i < primitiveCount; i++) {
|
||||
dstSrcMap[i] = i;
|
||||
}
|
||||
dstSrcMap.sort((indexA, indexB) =>
|
||||
sortFn(this.get(indexA, a), this.get(indexB, b)),
|
||||
);
|
||||
|
||||
// Mapping from PREVIOUS index to NEW index.
|
||||
for (let i = 0; i < primitiveCount; i++) {
|
||||
result[dstSrcMap[i]] = i;
|
||||
}
|
||||
|
||||
// Copy primitives to temporary collection, in sort order.
|
||||
const tmp = CollectionClass._cloneEmpty(this);
|
||||
for (let i = 0; i < primitiveCount; i++) {
|
||||
const src = this.get(dstSrcMap[i], a);
|
||||
const dst = tmp.add({}, b);
|
||||
PrimitiveClass.clone(src, dst);
|
||||
}
|
||||
|
||||
// Assign buffers from temporary collection onto this one.
|
||||
CollectionClass._replaceBuffers(tmp, this);
|
||||
this._dirtyOffset = 0;
|
||||
this._dirtyCount = primitiveCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates the contents of this collection into the result collection.
|
||||
* Result collection is not resized, and must contain enough space for all
|
||||
* primitives in the source collection. Existing primitives in the result
|
||||
* collection will be overwritten.
|
||||
*
|
||||
* <p>Useful when allocating more space for a collection that has reached its
|
||||
* capacity, and efficiently transferring features to the new collection.</p>
|
||||
*
|
||||
* @example
|
||||
* const result = new BufferPrimitiveCollection({ ... }); // allocate larger 'result' collection
|
||||
* BufferPrimitiveCollection.clone(collection, result); // copy primitives from 'collection' into 'result'
|
||||
*
|
||||
* @param {BufferPrimitiveCollection<T>} collection
|
||||
* @param {BufferPrimitiveCollection<T>} result
|
||||
* @template T extends BufferPrimitive
|
||||
*/
|
||||
static clone(collection, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
const { ERR_CAPACITY } = BufferPrimitiveCollection.Error;
|
||||
assert(collection.primitiveCount <= result.primitiveCountMax, ERR_CAPACITY);
|
||||
assert(collection.vertexCount <= result.vertexCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const layout = collection._getPrimitiveClass().Layout;
|
||||
const MaterialClass = collection._getMaterialClass();
|
||||
const PrimitiveClass = collection._getPrimitiveClass();
|
||||
|
||||
this._copySubDataView(
|
||||
collection._primitiveView,
|
||||
result._primitiveView,
|
||||
collection.primitiveCount * layout.__BYTE_LENGTH,
|
||||
);
|
||||
|
||||
this._copySubArray(
|
||||
collection._positionView,
|
||||
result._positionView,
|
||||
collection.vertexCount * 3,
|
||||
);
|
||||
|
||||
this._copySubDataView(
|
||||
collection._materialView,
|
||||
result._materialView,
|
||||
collection.primitiveCount * MaterialClass.packedLength,
|
||||
);
|
||||
|
||||
result.show = collection.show;
|
||||
result.debugShowBoundingVolume = collection.debugShowBoundingVolume;
|
||||
result._primitiveCount = collection._primitiveCount;
|
||||
result._positionCount = collection._positionCount;
|
||||
|
||||
// Unset PickIds.
|
||||
const primitive = new PrimitiveClass();
|
||||
for (let i = 0, il = result.primitiveCount; i < il; i++) {
|
||||
result.get(i, primitive)._pickId = 0;
|
||||
}
|
||||
|
||||
result._dirtyOffset = 0;
|
||||
result._dirtyCount = result.primitiveCount;
|
||||
|
||||
collection.boundingVolume.clone(result.boundingVolume);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty collection with the same buffer sizes as this collection.
|
||||
* Internal utility for operations requiring a working copy of memory.
|
||||
*
|
||||
* @param {BufferPrimitiveCollection<T>} collection
|
||||
* @returns {BufferPrimitiveCollection<T>}
|
||||
* @template T extends BufferPrimitive
|
||||
* @protected
|
||||
* @abstract
|
||||
* @ignore
|
||||
*/
|
||||
static _cloneEmpty(collection) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns buffers from source collection to target collection, without
|
||||
* validation or side effects. Callers must handle any validation, dirty
|
||||
* flag updates, etc.
|
||||
*
|
||||
* @param {BufferPrimitiveCollection<T>} src
|
||||
* @param {BufferPrimitiveCollection<T>} dst
|
||||
* @template T extends BufferPrimitive
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
static _replaceBuffers(src, dst) {
|
||||
dst._primitiveView = src._primitiveView;
|
||||
dst._positionView = src._positionView;
|
||||
dst._materialView = src._materialView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds collection bounding volume.
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
_updateBoundingVolume() {
|
||||
// Exclude unused space in the position buffer.
|
||||
let vertices = this._positionView.subarray(0, this._positionCount * 3);
|
||||
|
||||
if (this._positionNormalized) {
|
||||
vertices = AttributeCompression.dequantize(
|
||||
/** @type {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array} */ (
|
||||
vertices
|
||||
),
|
||||
this._positionDatatype,
|
||||
AttributeType.VEC3,
|
||||
this._positionCount,
|
||||
);
|
||||
}
|
||||
|
||||
BoundingSphere.fromVertices(
|
||||
vertices,
|
||||
Cartesian3.ZERO,
|
||||
3,
|
||||
this.boundingVolume,
|
||||
);
|
||||
BoundingSphere.transform(
|
||||
this.boundingVolume,
|
||||
this.modelMatrix,
|
||||
this.boundingVolume,
|
||||
);
|
||||
this._dirtyBoundingVolume = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates PickIds for the given context.
|
||||
* @param {Context} context
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
_updatePickIds(context) {
|
||||
let pickIds = this._pickIds.get(context);
|
||||
if (pickIds && pickIds.length === this._primitiveCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pickIds) {
|
||||
pickIds = [];
|
||||
this._pickIds.set(context, pickIds);
|
||||
}
|
||||
|
||||
const collection = this;
|
||||
const PrimitiveClass = this._getPrimitiveClass();
|
||||
const primitive = new PrimitiveClass();
|
||||
|
||||
// Fill in missing PickIDs for recently-added primitives.
|
||||
for (let i = pickIds.length, il = this._primitiveCount; i < il; i++) {
|
||||
this.get(i, primitive);
|
||||
|
||||
const pickObject = this._pickObjects[i] || {
|
||||
collection: this,
|
||||
index: i,
|
||||
get primitive() {
|
||||
// Cannot reuse primitives; scene.drillPick() appends to a list.
|
||||
return collection.get(i, new PrimitiveClass());
|
||||
},
|
||||
};
|
||||
|
||||
const pickId = context.createPickId(pickObject);
|
||||
primitive._pickId = pickId.key;
|
||||
pickIds.push(pickId);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// PRIMITIVE LIFECYCLE
|
||||
|
||||
/**
|
||||
* Makes the given {@link BufferPrimitive} a view onto this collection's
|
||||
* primitive at the given index, for use when reading/writing primitive
|
||||
* properties. When iterating over a large collection, prefer to reuse
|
||||
* the same BufferPrimitive instance throughout the loop — rebinding
|
||||
* an existing instance to a different primitive is cheap, and avoids
|
||||
* allocating in-memory objects for every object.
|
||||
*
|
||||
* @example
|
||||
* const primitive = new BufferPrimitive();
|
||||
* for (let i = 0; i < collection.primitiveCount; i++) {
|
||||
* collection.get(i, primitive);
|
||||
* primitive.setColor(Color.RED);
|
||||
* }
|
||||
*
|
||||
* @param {number} index
|
||||
* @param {BufferPrimitive} result
|
||||
* @returns {BufferPrimitive} The BufferPrimitive instance passed as the
|
||||
* 'result' argument, now bound to the specified primitive index.
|
||||
*/
|
||||
get(index, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number.greaterThanOrEquals("index", index, 0);
|
||||
Check.typeOf.number.lessThan("index", index, this._primitiveCount);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
result._collection = this;
|
||||
result._index = index;
|
||||
result._byteOffset = index * this._getPrimitiveClass().Layout.__BYTE_LENGTH;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new primitive to the collection, with the specified options. A
|
||||
* {@link BufferPrimitive} instance is linked to the new primitive, using
|
||||
* the 'result' argument if given, or a new instance if not. For repeated
|
||||
* calls, prefer to reuse a single BufferPrimitive instance rather than
|
||||
* allocating a new instance on each call.
|
||||
*
|
||||
* @param {BufferPrimitiveOptions} options
|
||||
* @param {BufferPrimitive} result
|
||||
* @returns {BufferPrimitive}
|
||||
*/
|
||||
add(options = Frozen.EMPTY_OBJECT, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
const { ERR_CAPACITY } = BufferPrimitiveCollection.Error;
|
||||
assert(this.primitiveCount < this.primitiveCountMax, ERR_CAPACITY);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const MaterialClass = this._getMaterialClass();
|
||||
const index = this._primitiveCount++;
|
||||
|
||||
result = this.get(index, result);
|
||||
result.featureId = options.featureId ?? index;
|
||||
result.show = options.show ?? true;
|
||||
result.setMaterial(options.material ?? MaterialClass.DEFAULT_MATERIAL);
|
||||
result._pickId = 0; // unset
|
||||
result._dirty = true;
|
||||
|
||||
if (defined(options.pickObject)) {
|
||||
this._pickObjects[index] = options.pickObject;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks primitive at given index as 'dirty', to be updated on next render.
|
||||
* @param {number} index
|
||||
* @ignore
|
||||
*/
|
||||
_makeDirty(index) {
|
||||
if (this._dirtyCount === 0) {
|
||||
this._dirtyCount = 1;
|
||||
this._dirtyOffset = index;
|
||||
} else if (index < this._dirtyOffset) {
|
||||
this._dirtyCount += this._dirtyOffset - index;
|
||||
this._dirtyOffset = index;
|
||||
} else if (index + 1 > this._dirtyOffset + this._dirtyCount) {
|
||||
this._dirtyCount = index + 1 - this._dirtyOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks collection bounding volume as 'dirty', to be updated on next render,
|
||||
* if automatic bounding volume updates are enabled.
|
||||
* @ignore
|
||||
*/
|
||||
_makeDirtyBoundingVolume() {
|
||||
if (this._boundingVolumeAutoUpdate) {
|
||||
this._dirtyBoundingVolume = true;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// RENDER
|
||||
|
||||
/** @param {object} frameState */
|
||||
update(frameState) {
|
||||
if (/** @type {FrameState} */ (frameState).mode !== SceneMode.SCENE3D) {
|
||||
oneTimeWarning(
|
||||
"bufferprim-scenemode",
|
||||
"BufferPrimitiveCollection requires SceneMode.SCENE3D.",
|
||||
);
|
||||
}
|
||||
|
||||
if (this._dirtyBoundingVolume) {
|
||||
this._updateBoundingVolume();
|
||||
}
|
||||
if (this._allowPicking && this._dirtyCount > 0) {
|
||||
this._updatePickIds(/** @type {FrameState} */ (frameState).context);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// ACCESSORS
|
||||
|
||||
/**
|
||||
* Number of primitives in collection. Must be <= {@link primitiveCountMax}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get primitiveCount() {
|
||||
return this._primitiveCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of primitives this collection can contain. Must be >=
|
||||
* {@link primitiveCount}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY}
|
||||
*/
|
||||
get primitiveCountMax() {
|
||||
return this._primitiveCountMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total byte length of buffers owned by this collection. Includes any unused
|
||||
* space allocated by {@link primitiveCountMax}, even if no primitives have
|
||||
* yet been added in that space.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get byteLength() {
|
||||
return (
|
||||
this._primitiveView.byteLength +
|
||||
this._positionView.byteLength +
|
||||
this._materialView.byteLength
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of vertices in collection. Must be <= {@link vertexCountMax}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get vertexCount() {
|
||||
return this._positionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of vertices this collection can contain. Must be >=
|
||||
* {@link vertexCount}.
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @default {@link BufferPrimitiveCollection.DEFAULT_CAPACITY}
|
||||
*/
|
||||
get vertexCountMax() {
|
||||
return this._positionCountMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms geometry from model to world coordinates.
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
* @readonly
|
||||
*/
|
||||
get modelMatrix() {
|
||||
return this._modelMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* World-space bounding volume for all primitives in the collection, including both
|
||||
* shown and hidden primitives.
|
||||
* @type {BoundingSphere}
|
||||
* @readonly
|
||||
*/
|
||||
get boundingVolume() {
|
||||
return this._boundingVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* The component datatype used to store position values.
|
||||
* @type {ComponentDatatype}
|
||||
* @readonly
|
||||
*/
|
||||
get positionDatatype() {
|
||||
return this._positionDatatype;
|
||||
}
|
||||
|
||||
/**
|
||||
* When <code>true</code>, integer position values are treated as normalized
|
||||
* values, where the full integer range maps to [-1, 1] (signed) or [0, 1]
|
||||
* (unsigned).
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get positionNormalized() {
|
||||
return this._positionNormalized;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// UTILS
|
||||
|
||||
/**
|
||||
* @param {TypedArray} src
|
||||
* @param {TypedArray} dst
|
||||
* @param {number} count
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
static _copySubArray(src, dst, count) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DataView} src
|
||||
* @param {DataView} dst
|
||||
* @param {number} byteLength
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
static _copySubDataView(src, dst, byteLength) {
|
||||
// No need to match the original array type, just copy in 4-byte chunks.
|
||||
this._copySubArray(
|
||||
new Uint32Array(src.buffer, src.byteOffset, src.byteLength / 4),
|
||||
new Uint32Array(dst.buffer, dst.byteOffset, dst.byteLength / 4),
|
||||
byteLength / 4,
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable array representing the collection. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @example
|
||||
* console.table(collection.toJSON());
|
||||
*
|
||||
* @returns {Array<Object>} List of JSON-serializable objects, one for each
|
||||
* primitive in the collection.
|
||||
*/
|
||||
toJSON() {
|
||||
const PrimitiveClass = this._getPrimitiveClass();
|
||||
const primitive = new PrimitiveClass();
|
||||
|
||||
const results = [];
|
||||
for (let i = 0, il = this.primitiveCount; i < il; i++) {
|
||||
results.push(this.get(i, primitive).toJSON());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default capacity of buffers on new collections. A quantity of elements:
|
||||
* number of vertices in the vertex buffer, primitives in the primitive
|
||||
* buffer, etc. This value is arbitrary, and collections cannot be resized,
|
||||
* so specific per-buffer capacities should be provided in the collection
|
||||
* constructor when available.
|
||||
*
|
||||
* @type {number}
|
||||
* @static
|
||||
* @constant
|
||||
*/
|
||||
BufferPrimitiveCollection.DEFAULT_CAPACITY = 1024;
|
||||
|
||||
export default BufferPrimitiveCollection;
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
// @ts-check
|
||||
|
||||
import Color from "../Core/Color.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
|
||||
/** @import Packable from "../Core/Packable.js"; */
|
||||
/** @import BufferPrimitive from "./BufferPrimitive.js"; */
|
||||
|
||||
/**
|
||||
* @typedef {object} BufferPrimitiveMaterialOptions
|
||||
* @property {Color} [color=Color.WHITE] Color of fill.
|
||||
* @property {Color} [outlineColor=Color.WHITE] Color of outline.
|
||||
* @property {number} [outlineWidth=0.0] Width of outline, 0-255px.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Material description for a {@link BufferPrimitive}. Abstract.
|
||||
*
|
||||
* <p>BufferPrimitiveMaterial objects are {@link Packable|packable}, stored
|
||||
* when calling {@link BufferPrimitive#setMaterial}. Subsequent changes to the
|
||||
* material will not affect the primitive until setMaterial() is called again.</p>
|
||||
*
|
||||
* @see BufferPointMaterial
|
||||
* @see BufferPolylineMaterial
|
||||
* @see BufferPolygonMaterial
|
||||
* @see Packable
|
||||
*
|
||||
* @abstract
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
class BufferPrimitiveMaterial {
|
||||
/** @ignore */
|
||||
static Layout = {
|
||||
COLOR_U32: 0,
|
||||
OUTLINE_COLOR_U32: 4,
|
||||
OUTLINE_WIDTH_U8: 8,
|
||||
__BYTE_LENGTH: 12,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {BufferPrimitiveMaterial}
|
||||
* @ignore
|
||||
*/
|
||||
static DEFAULT_MATERIAL;
|
||||
|
||||
/**
|
||||
* @param {BufferPrimitiveMaterialOptions} [options]
|
||||
*/
|
||||
constructor(options = Frozen.EMPTY_OBJECT) {
|
||||
/**
|
||||
* Color of fill.
|
||||
* @type {Color}
|
||||
*/
|
||||
this.color = Color.clone(options.color ?? Color.WHITE);
|
||||
|
||||
/**
|
||||
* Color of outline.
|
||||
* @type {Color}
|
||||
*/
|
||||
this.outlineColor = Color.clone(options.outlineColor ?? Color.WHITE);
|
||||
|
||||
/**
|
||||
* Width of outline, 0-255px.
|
||||
* @type {number}
|
||||
*/
|
||||
this.outlineWidth = options.outlineWidth ?? 0;
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
static get packedLength() {
|
||||
return this.Layout.__BYTE_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the provided material into the provided array.
|
||||
*
|
||||
* @param {BufferPrimitiveMaterial} material
|
||||
* @param {DataView} view
|
||||
* @param {number} byteOffset
|
||||
*/
|
||||
static pack(material, view, byteOffset) {
|
||||
view.setUint32(
|
||||
this.Layout.COLOR_U32 + byteOffset,
|
||||
material.color.toRgba(),
|
||||
true,
|
||||
);
|
||||
view.setUint32(
|
||||
this.Layout.OUTLINE_COLOR_U32 + byteOffset,
|
||||
material.outlineColor.toRgba(),
|
||||
true,
|
||||
);
|
||||
view.setUint8(
|
||||
this.Layout.OUTLINE_WIDTH_U8 + byteOffset,
|
||||
material.outlineWidth,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a material from a packed array.
|
||||
*
|
||||
* @param {DataView} view The packed array.
|
||||
* @param {number} byteOffset Starting index of the element to be unpacked.
|
||||
* @param {BufferPrimitiveMaterial} result Material into which results are unpacked.
|
||||
* @returns {BufferPrimitiveMaterial} Modified result material, with results unpacked.
|
||||
*/
|
||||
static unpack(view, byteOffset, result) {
|
||||
Color.fromRgba(
|
||||
view.getUint32(this.Layout.COLOR_U32 + byteOffset, true),
|
||||
result.color,
|
||||
);
|
||||
Color.fromRgba(
|
||||
view.getUint32(this.Layout.OUTLINE_COLOR_U32 + byteOffset, true),
|
||||
result.outlineColor,
|
||||
);
|
||||
result.outlineWidth = view.getUint8(
|
||||
this.Layout.OUTLINE_WIDTH_U8 + byteOffset,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// DEBUG
|
||||
|
||||
/**
|
||||
* Returns a JSON-serializable object representing the material. This encoding
|
||||
* is not memory-efficient, and should generally be used for debugging and
|
||||
* testing.
|
||||
*
|
||||
* @returns {Object} JSON-serializable object.
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
color: this.color.toCssHexString(),
|
||||
outlineColor: this.outlineColor.toCssHexString(),
|
||||
outlineWidth: this.outlineWidth,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferPrimitiveMaterial;
|
||||
+3989
File diff suppressed because it is too large
Load Diff
+631
@@ -0,0 +1,631 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import KeyboardEventModifier from "../Core/KeyboardEventModifier.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import ScreenSpaceEventHandler from "../Core/ScreenSpaceEventHandler.js";
|
||||
import ScreenSpaceEventType from "../Core/ScreenSpaceEventType.js";
|
||||
import CameraEventType from "./CameraEventType.js";
|
||||
|
||||
const keyboardModifierCombinations = [
|
||||
KeyboardEventModifier.SHIFT,
|
||||
KeyboardEventModifier.CTRL,
|
||||
KeyboardEventModifier.ALT,
|
||||
[KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL],
|
||||
[KeyboardEventModifier.SHIFT, KeyboardEventModifier.ALT],
|
||||
[KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT],
|
||||
[
|
||||
KeyboardEventModifier.SHIFT,
|
||||
KeyboardEventModifier.CTRL,
|
||||
KeyboardEventModifier.ALT,
|
||||
],
|
||||
];
|
||||
|
||||
function getKey(type, modifiers) {
|
||||
if (!defined(modifiers)) {
|
||||
return `${type}`;
|
||||
}
|
||||
|
||||
const modifierList = Array.isArray(modifiers)
|
||||
? modifiers.toSorted()
|
||||
: [modifiers];
|
||||
return `${type}+${modifierList.join("+")}`;
|
||||
}
|
||||
|
||||
function clonePinchMovement(pinchMovement, result) {
|
||||
Cartesian2.clone(
|
||||
pinchMovement.distance.startPosition,
|
||||
result.distance.startPosition,
|
||||
);
|
||||
Cartesian2.clone(
|
||||
pinchMovement.distance.endPosition,
|
||||
result.distance.endPosition,
|
||||
);
|
||||
|
||||
Cartesian2.clone(
|
||||
pinchMovement.angleAndHeight.startPosition,
|
||||
result.angleAndHeight.startPosition,
|
||||
);
|
||||
Cartesian2.clone(
|
||||
pinchMovement.angleAndHeight.endPosition,
|
||||
result.angleAndHeight.endPosition,
|
||||
);
|
||||
}
|
||||
|
||||
function listenToPinch(aggregator, modifier, canvas) {
|
||||
const key = getKey(CameraEventType.PINCH, modifier);
|
||||
|
||||
const update = aggregator._update;
|
||||
const isDown = aggregator._isDown;
|
||||
const eventStartPosition = aggregator._eventStartPosition;
|
||||
const pressTime = aggregator._pressTime;
|
||||
const releaseTime = aggregator._releaseTime;
|
||||
|
||||
update[key] = true;
|
||||
isDown[key] = false;
|
||||
eventStartPosition[key] = new Cartesian2();
|
||||
|
||||
let movement = aggregator._movement[key];
|
||||
if (!defined(movement)) {
|
||||
movement = aggregator._movement[key] = {};
|
||||
}
|
||||
|
||||
movement.distance = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
};
|
||||
movement.angleAndHeight = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
};
|
||||
movement.prevAngle = 0.0;
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function (event) {
|
||||
aggregator._buttonsDown++;
|
||||
isDown[key] = true;
|
||||
pressTime[key] = new Date();
|
||||
// Compute center position and store as start point.
|
||||
Cartesian2.lerp(
|
||||
event.position1,
|
||||
event.position2,
|
||||
0.5,
|
||||
eventStartPosition[key],
|
||||
);
|
||||
},
|
||||
ScreenSpaceEventType.PINCH_START,
|
||||
modifier,
|
||||
);
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function () {
|
||||
aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
|
||||
isDown[key] = false;
|
||||
releaseTime[key] = new Date();
|
||||
},
|
||||
ScreenSpaceEventType.PINCH_END,
|
||||
modifier,
|
||||
);
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function (mouseMovement) {
|
||||
if (isDown[key]) {
|
||||
// Aggregate several input events into a single animation frame.
|
||||
if (!update[key]) {
|
||||
Cartesian2.clone(
|
||||
mouseMovement.distance.endPosition,
|
||||
movement.distance.endPosition,
|
||||
);
|
||||
Cartesian2.clone(
|
||||
mouseMovement.angleAndHeight.endPosition,
|
||||
movement.angleAndHeight.endPosition,
|
||||
);
|
||||
} else {
|
||||
clonePinchMovement(mouseMovement, movement);
|
||||
update[key] = false;
|
||||
movement.prevAngle = movement.angleAndHeight.startPosition.x;
|
||||
}
|
||||
// Make sure our aggregation of angles does not "flip" over 360 degrees.
|
||||
let angle = movement.angleAndHeight.endPosition.x;
|
||||
const prevAngle = movement.prevAngle;
|
||||
const TwoPI = Math.PI * 2;
|
||||
while (angle >= prevAngle + Math.PI) {
|
||||
angle -= TwoPI;
|
||||
}
|
||||
while (angle < prevAngle - Math.PI) {
|
||||
angle += TwoPI;
|
||||
}
|
||||
movement.angleAndHeight.endPosition.x =
|
||||
(-angle * canvas.clientWidth) / 12;
|
||||
movement.angleAndHeight.startPosition.x =
|
||||
(-prevAngle * canvas.clientWidth) / 12;
|
||||
}
|
||||
},
|
||||
ScreenSpaceEventType.PINCH_MOVE,
|
||||
modifier,
|
||||
);
|
||||
}
|
||||
|
||||
function listenToWheel(aggregator, modifier) {
|
||||
const key = getKey(CameraEventType.WHEEL, modifier);
|
||||
|
||||
const pressTime = aggregator._pressTime;
|
||||
const releaseTime = aggregator._releaseTime;
|
||||
|
||||
const update = aggregator._update;
|
||||
update[key] = true;
|
||||
|
||||
let movement = aggregator._movement[key];
|
||||
if (!defined(movement)) {
|
||||
movement = aggregator._movement[key] = {};
|
||||
}
|
||||
|
||||
let lastMovement = aggregator._lastMovement[key];
|
||||
if (!defined(lastMovement)) {
|
||||
lastMovement = aggregator._lastMovement[key] = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
|
||||
movement.startPosition = new Cartesian2();
|
||||
Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);
|
||||
movement.endPosition = new Cartesian2();
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function (delta) {
|
||||
const arcLength = 7.5 * CesiumMath.toRadians(delta);
|
||||
pressTime[key] = releaseTime[key] = new Date();
|
||||
movement.endPosition.x = 0.0;
|
||||
movement.endPosition.y = arcLength;
|
||||
Cartesian2.clone(movement.endPosition, lastMovement.endPosition);
|
||||
lastMovement.valid = true;
|
||||
update[key] = false;
|
||||
},
|
||||
ScreenSpaceEventType.WHEEL,
|
||||
modifier,
|
||||
);
|
||||
}
|
||||
|
||||
function listenMouseButtonDownUp(aggregator, modifier, type) {
|
||||
const key = getKey(type, modifier);
|
||||
|
||||
const isDown = aggregator._isDown;
|
||||
const eventStartPosition = aggregator._eventStartPosition;
|
||||
const pressTime = aggregator._pressTime;
|
||||
|
||||
isDown[key] = false;
|
||||
eventStartPosition[key] = new Cartesian2();
|
||||
|
||||
let lastMovement = aggregator._lastMovement[key];
|
||||
if (!defined(lastMovement)) {
|
||||
lastMovement = aggregator._lastMovement[key] = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
|
||||
let down;
|
||||
let up;
|
||||
if (type === CameraEventType.LEFT_DRAG) {
|
||||
down = ScreenSpaceEventType.LEFT_DOWN;
|
||||
up = ScreenSpaceEventType.LEFT_UP;
|
||||
} else if (type === CameraEventType.RIGHT_DRAG) {
|
||||
down = ScreenSpaceEventType.RIGHT_DOWN;
|
||||
up = ScreenSpaceEventType.RIGHT_UP;
|
||||
} else if (type === CameraEventType.MIDDLE_DRAG) {
|
||||
down = ScreenSpaceEventType.MIDDLE_DOWN;
|
||||
up = ScreenSpaceEventType.MIDDLE_UP;
|
||||
}
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function (event) {
|
||||
aggregator._buttonsDown++;
|
||||
lastMovement.valid = false;
|
||||
isDown[key] = true;
|
||||
pressTime[key] = new Date();
|
||||
Cartesian2.clone(event.position, eventStartPosition[key]);
|
||||
},
|
||||
down,
|
||||
modifier,
|
||||
);
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function () {
|
||||
cancelMouseDownAction(getKey(type, undefined), aggregator);
|
||||
for (const modifier of keyboardModifierCombinations) {
|
||||
const cancelKey = getKey(type, modifier);
|
||||
cancelMouseDownAction(cancelKey, aggregator);
|
||||
}
|
||||
},
|
||||
up,
|
||||
modifier,
|
||||
);
|
||||
}
|
||||
|
||||
function cancelMouseDownAction(cancelKey, aggregator) {
|
||||
const releaseTime = aggregator._releaseTime;
|
||||
const isDown = aggregator._isDown;
|
||||
if (isDown[cancelKey]) {
|
||||
aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
|
||||
}
|
||||
isDown[cancelKey] = false;
|
||||
releaseTime[cancelKey] = new Date();
|
||||
}
|
||||
|
||||
function cloneMouseMovement(mouseMovement, result) {
|
||||
Cartesian2.clone(mouseMovement.startPosition, result.startPosition);
|
||||
Cartesian2.clone(mouseMovement.endPosition, result.endPosition);
|
||||
}
|
||||
|
||||
function refreshMouseDownStatus(type, modifier, aggregator) {
|
||||
// first: Judge if the mouse is pressed
|
||||
const isDown = aggregator._isDown;
|
||||
let anyButtonIsDown = false;
|
||||
const currentKey = getKey(type, modifier);
|
||||
for (const [downKey, downValue] of Object.entries(isDown)) {
|
||||
if (downKey.startsWith(type) && downValue && downKey !== currentKey) {
|
||||
anyButtonIsDown = true;
|
||||
cancelMouseDownAction(downKey, aggregator);
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyButtonIsDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// second: If it is pressed, it will be transferred to the current modifier.
|
||||
const pressTime = aggregator._pressTime;
|
||||
let lastMovement = aggregator._lastMovement[currentKey];
|
||||
if (!defined(lastMovement)) {
|
||||
lastMovement = aggregator._lastMovement[currentKey] = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
aggregator._buttonsDown++;
|
||||
lastMovement.valid = false;
|
||||
isDown[currentKey] = true;
|
||||
pressTime[currentKey] = new Date();
|
||||
}
|
||||
|
||||
function listenMouseMove(aggregator, modifier) {
|
||||
const update = aggregator._update;
|
||||
const movement = aggregator._movement;
|
||||
const lastMovement = aggregator._lastMovement;
|
||||
const isDown = aggregator._isDown;
|
||||
|
||||
for (const typeName in CameraEventType) {
|
||||
if (CameraEventType.hasOwnProperty(typeName)) {
|
||||
const type = CameraEventType[typeName];
|
||||
if (defined(type)) {
|
||||
const key = getKey(type, modifier);
|
||||
update[key] = true;
|
||||
|
||||
if (!defined(aggregator._lastMovement[key])) {
|
||||
aggregator._lastMovement[key] = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!defined(aggregator._movement[key])) {
|
||||
aggregator._movement[key] = {
|
||||
startPosition: new Cartesian2(),
|
||||
endPosition: new Cartesian2(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aggregator._eventHandler.setInputAction(
|
||||
function (mouseMovement) {
|
||||
for (const typeName in CameraEventType) {
|
||||
if (CameraEventType.hasOwnProperty(typeName)) {
|
||||
const type = CameraEventType[typeName];
|
||||
if (defined(type)) {
|
||||
const key = getKey(type, modifier);
|
||||
refreshMouseDownStatus(type, modifier, aggregator);
|
||||
if (isDown[key]) {
|
||||
if (!update[key]) {
|
||||
Cartesian2.clone(
|
||||
mouseMovement.endPosition,
|
||||
movement[key].endPosition,
|
||||
);
|
||||
} else {
|
||||
cloneMouseMovement(movement[key], lastMovement[key]);
|
||||
lastMovement[key].valid = true;
|
||||
cloneMouseMovement(mouseMovement, movement[key]);
|
||||
update[key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cartesian2.clone(
|
||||
mouseMovement.endPosition,
|
||||
aggregator._currentMousePosition,
|
||||
);
|
||||
},
|
||||
ScreenSpaceEventType.MOUSE_MOVE,
|
||||
modifier,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates input events. For example, suppose the following inputs are received between frames:
|
||||
* left mouse button down, mouse move, mouse move, left mouse button up. These events will be aggregated into
|
||||
* one event with a start and end position of the mouse.
|
||||
*
|
||||
* @alias CameraEventAggregator
|
||||
* @constructor
|
||||
*
|
||||
* @param {HTMLCanvasElement} [canvas=document] The element to handle events for.
|
||||
*
|
||||
* @see ScreenSpaceEventHandler
|
||||
*/
|
||||
function CameraEventAggregator(canvas) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(canvas)) {
|
||||
throw new DeveloperError("canvas is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._eventHandler = new ScreenSpaceEventHandler(canvas);
|
||||
|
||||
this._update = {};
|
||||
this._movement = {};
|
||||
this._lastMovement = {};
|
||||
this._isDown = {};
|
||||
this._eventStartPosition = {};
|
||||
this._pressTime = {};
|
||||
this._releaseTime = {};
|
||||
|
||||
this._buttonsDown = 0;
|
||||
|
||||
this._currentMousePosition = new Cartesian2();
|
||||
|
||||
listenToWheel(this, undefined);
|
||||
listenToPinch(this, undefined, canvas);
|
||||
listenMouseButtonDownUp(this, undefined, CameraEventType.LEFT_DRAG);
|
||||
listenMouseButtonDownUp(this, undefined, CameraEventType.RIGHT_DRAG);
|
||||
listenMouseButtonDownUp(this, undefined, CameraEventType.MIDDLE_DRAG);
|
||||
listenMouseMove(this, undefined);
|
||||
|
||||
for (const modifiers of keyboardModifierCombinations) {
|
||||
listenToWheel(this, modifiers);
|
||||
listenToPinch(this, modifiers, canvas);
|
||||
listenMouseButtonDownUp(this, modifiers, CameraEventType.LEFT_DRAG);
|
||||
listenMouseButtonDownUp(this, modifiers, CameraEventType.RIGHT_DRAG);
|
||||
listenMouseButtonDownUp(this, modifiers, CameraEventType.MIDDLE_DRAG);
|
||||
listenMouseMove(this, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(CameraEventAggregator.prototype, {
|
||||
/**
|
||||
* Gets the current mouse position.
|
||||
* @memberof CameraEventAggregator.prototype
|
||||
* @type {Cartesian2}
|
||||
*/
|
||||
currentMousePosition: {
|
||||
get: function () {
|
||||
return this._currentMousePosition;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets whether any mouse button is down, a touch has started, or the wheel has been moved.
|
||||
* @memberof CameraEventAggregator.prototype
|
||||
* @type {boolean}
|
||||
*/
|
||||
anyButtonDown: {
|
||||
get: function () {
|
||||
const wheelMoved =
|
||||
!this._update[getKey(CameraEventType.WHEEL)] ||
|
||||
!this._update[
|
||||
getKey(CameraEventType.WHEEL, KeyboardEventModifier.SHIFT)
|
||||
] ||
|
||||
!this._update[
|
||||
getKey(CameraEventType.WHEEL, KeyboardEventModifier.CTRL)
|
||||
] ||
|
||||
!this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.ALT)];
|
||||
return this._buttonsDown > 0 || wheelMoved;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets if a mouse button down or touch has started and has been moved.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {boolean} Returns <code>true</code> if a mouse button down or touch has started and has been moved; otherwise, <code>false</code>
|
||||
*/
|
||||
CameraEventAggregator.prototype.isMoving = function (type, modifier) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
return !this._update[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the aggregated start and end position of the current event.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {object} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code>.
|
||||
*/
|
||||
CameraEventAggregator.prototype.getMovement = function (type, modifier) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
const movement = this._movement[key];
|
||||
return movement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the start and end position of the last move event (not the aggregated event).
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {object|undefined} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code> or <code>undefined</code>.
|
||||
*/
|
||||
CameraEventAggregator.prototype.getLastMovement = function (type, modifier) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
const lastMovement = this._lastMovement[key];
|
||||
if (lastMovement.valid) {
|
||||
return lastMovement;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets whether the mouse button is down or a touch has started.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {boolean} Whether the mouse button is down or a touch has started.
|
||||
*/
|
||||
CameraEventAggregator.prototype.isButtonDown = function (type, modifier) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
return this._isDown[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the mouse position that started the aggregation.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {Cartesian2} The mouse position.
|
||||
*/
|
||||
CameraEventAggregator.prototype.getStartMousePosition = function (
|
||||
type,
|
||||
modifier,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (type === CameraEventType.WHEEL) {
|
||||
return this._currentMousePosition;
|
||||
}
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
return this._eventStartPosition[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the time the button was pressed or the touch was started.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {Date} The time the button was pressed or the touch was started.
|
||||
*/
|
||||
CameraEventAggregator.prototype.getButtonPressTime = function (type, modifier) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
return this._pressTime[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the time the button was released or the touch was ended.
|
||||
*
|
||||
* @param {CameraEventType} type The camera event type.
|
||||
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
|
||||
* @returns {Date} The time the button was released or the touch was ended.
|
||||
*/
|
||||
CameraEventAggregator.prototype.getButtonReleaseTime = function (
|
||||
type,
|
||||
modifier,
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(type)) {
|
||||
throw new DeveloperError("type is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const key = getKey(type, modifier);
|
||||
return this._releaseTime[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Signals that all of the events have been handled and the aggregator should be reset to handle new events.
|
||||
*/
|
||||
CameraEventAggregator.prototype.reset = function () {
|
||||
for (const name in this._update) {
|
||||
if (this._update.hasOwnProperty(name)) {
|
||||
this._update[name] = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 CameraEventAggregator#destroy
|
||||
*/
|
||||
CameraEventAggregator.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes mouse listeners held by 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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* handler = handler && handler.destroy();
|
||||
*
|
||||
* @see CameraEventAggregator#isDestroyed
|
||||
*/
|
||||
CameraEventAggregator.prototype.destroy = function () {
|
||||
this._eventHandler = this._eventHandler && this._eventHandler.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default CameraEventAggregator;
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Enumerates the available input for interacting with the camera.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const CameraEventType = {
|
||||
/**
|
||||
* A left mouse button press followed by moving the mouse and releasing the button.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
LEFT_DRAG: 0,
|
||||
|
||||
/**
|
||||
* A right mouse button press followed by moving the mouse and releasing the button.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
RIGHT_DRAG: 1,
|
||||
|
||||
/**
|
||||
* A middle mouse button press followed by moving the mouse and releasing the button.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
MIDDLE_DRAG: 2,
|
||||
|
||||
/**
|
||||
* Scrolling the middle mouse button.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
WHEEL: 3,
|
||||
|
||||
/**
|
||||
* A two-finger touch on a touch surface.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
PINCH: 4,
|
||||
};
|
||||
|
||||
Object.freeze(CameraEventType);
|
||||
|
||||
export default CameraEventType;
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import EasingFunction from "../Core/EasingFunction.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
|
||||
import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js";
|
||||
import SceneMode from "./SceneMode.js";
|
||||
|
||||
/**
|
||||
* Creates tweens for camera flights.
|
||||
* <br /><br />
|
||||
* Mouse interaction is disabled during flights.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const CameraFlightPath = {};
|
||||
|
||||
function getAltitude(frustum, dx, dy) {
|
||||
let near;
|
||||
let top;
|
||||
let right;
|
||||
if (frustum instanceof PerspectiveFrustum) {
|
||||
const tanTheta = Math.tan(0.5 * frustum.fovy);
|
||||
near = frustum.near;
|
||||
top = frustum.near * tanTheta;
|
||||
right = frustum.aspectRatio * top;
|
||||
return Math.max((dx * near) / right, (dy * near) / top);
|
||||
} else if (frustum instanceof PerspectiveOffCenterFrustum) {
|
||||
near = frustum.near;
|
||||
top = frustum.top;
|
||||
right = frustum.right;
|
||||
return Math.max((dx * near) / right, (dy * near) / top);
|
||||
}
|
||||
|
||||
return Math.max(dx, dy);
|
||||
}
|
||||
|
||||
const scratchCart = new Cartesian3();
|
||||
const scratchCart2 = new Cartesian3();
|
||||
|
||||
function createPitchFunction(
|
||||
startPitch,
|
||||
endPitch,
|
||||
heightFunction,
|
||||
pitchAdjustHeight,
|
||||
) {
|
||||
if (defined(pitchAdjustHeight) && heightFunction(0.5) > pitchAdjustHeight) {
|
||||
const startHeight = heightFunction(0.0);
|
||||
const endHeight = heightFunction(1.0);
|
||||
const middleHeight = heightFunction(0.5);
|
||||
|
||||
const d1 = middleHeight - startHeight;
|
||||
const d2 = middleHeight - endHeight;
|
||||
|
||||
return function (time) {
|
||||
const altitude = heightFunction(time);
|
||||
if (time <= 0.5) {
|
||||
const t1 = (altitude - startHeight) / d1;
|
||||
return CesiumMath.lerp(startPitch, -CesiumMath.PI_OVER_TWO, t1);
|
||||
}
|
||||
|
||||
const t2 = (altitude - endHeight) / d2;
|
||||
return CesiumMath.lerp(-CesiumMath.PI_OVER_TWO, endPitch, 1 - t2);
|
||||
};
|
||||
}
|
||||
return function (time) {
|
||||
return CesiumMath.lerp(startPitch, endPitch, time);
|
||||
};
|
||||
}
|
||||
|
||||
function createHeightFunction(
|
||||
camera,
|
||||
destination,
|
||||
startHeight,
|
||||
endHeight,
|
||||
optionAltitude,
|
||||
) {
|
||||
let altitude = optionAltitude;
|
||||
const maxHeight = Math.max(startHeight, endHeight);
|
||||
|
||||
if (!defined(altitude)) {
|
||||
const start = camera.position;
|
||||
const end = destination;
|
||||
const up = camera.up;
|
||||
const right = camera.right;
|
||||
const frustum = camera.frustum;
|
||||
|
||||
const diff = Cartesian3.subtract(start, end, scratchCart);
|
||||
const verticalDistance = Cartesian3.magnitude(
|
||||
Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2),
|
||||
);
|
||||
const horizontalDistance = Cartesian3.magnitude(
|
||||
Cartesian3.multiplyByScalar(
|
||||
right,
|
||||
Cartesian3.dot(diff, right),
|
||||
scratchCart2,
|
||||
),
|
||||
);
|
||||
|
||||
altitude = Math.min(
|
||||
getAltitude(frustum, verticalDistance, horizontalDistance) * 0.2,
|
||||
1000000000.0,
|
||||
);
|
||||
}
|
||||
|
||||
if (maxHeight < altitude) {
|
||||
const power = 8.0;
|
||||
const factor = 1000000.0;
|
||||
|
||||
const s = -Math.pow((altitude - startHeight) * factor, 1.0 / power);
|
||||
const e = Math.pow((altitude - endHeight) * factor, 1.0 / power);
|
||||
|
||||
return function (t) {
|
||||
const x = t * (e - s) + s;
|
||||
return -Math.pow(x, power) / factor + altitude;
|
||||
};
|
||||
}
|
||||
|
||||
return function (t) {
|
||||
return CesiumMath.lerp(startHeight, endHeight, t);
|
||||
};
|
||||
}
|
||||
|
||||
function adjustAngleForLERP(startAngle, endAngle) {
|
||||
if (
|
||||
CesiumMath.equalsEpsilon(
|
||||
startAngle,
|
||||
CesiumMath.TWO_PI,
|
||||
CesiumMath.EPSILON11,
|
||||
)
|
||||
) {
|
||||
startAngle = 0.0;
|
||||
}
|
||||
|
||||
if (endAngle > startAngle + Math.PI) {
|
||||
startAngle += CesiumMath.TWO_PI;
|
||||
} else if (endAngle < startAngle - Math.PI) {
|
||||
startAngle -= CesiumMath.TWO_PI;
|
||||
}
|
||||
|
||||
return startAngle;
|
||||
}
|
||||
|
||||
const scratchStart = new Cartesian3();
|
||||
|
||||
function createUpdateCV(
|
||||
scene,
|
||||
duration,
|
||||
destination,
|
||||
heading,
|
||||
pitch,
|
||||
roll,
|
||||
optionAltitude,
|
||||
optionPitchAdjustHeight,
|
||||
) {
|
||||
const camera = scene.camera;
|
||||
|
||||
const start = Cartesian3.clone(camera.position, scratchStart);
|
||||
const startPitch = camera.pitch;
|
||||
const startHeading = adjustAngleForLERP(camera.heading, heading);
|
||||
const startRoll = adjustAngleForLERP(camera.roll, roll);
|
||||
|
||||
const heightFunction = createHeightFunction(
|
||||
camera,
|
||||
destination,
|
||||
start.z,
|
||||
destination.z,
|
||||
optionAltitude,
|
||||
);
|
||||
|
||||
const pitchFunction = createPitchFunction(
|
||||
startPitch,
|
||||
pitch,
|
||||
heightFunction,
|
||||
optionPitchAdjustHeight,
|
||||
);
|
||||
|
||||
function update(value) {
|
||||
const time = value.time / duration;
|
||||
|
||||
camera.setView({
|
||||
orientation: {
|
||||
heading: CesiumMath.lerp(startHeading, heading, time),
|
||||
pitch: pitchFunction(time),
|
||||
roll: CesiumMath.lerp(startRoll, roll, time),
|
||||
},
|
||||
});
|
||||
|
||||
Cartesian2.lerp(start, destination, time, camera.position);
|
||||
camera.position.z = heightFunction(time);
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
function useLongestFlight(startCart, destCart) {
|
||||
if (startCart.longitude < destCart.longitude) {
|
||||
startCart.longitude += CesiumMath.TWO_PI;
|
||||
} else {
|
||||
destCart.longitude += CesiumMath.TWO_PI;
|
||||
}
|
||||
}
|
||||
|
||||
function useShortestFlight(startCart, destCart) {
|
||||
const diff = startCart.longitude - destCart.longitude;
|
||||
if (diff < -CesiumMath.PI) {
|
||||
startCart.longitude += CesiumMath.TWO_PI;
|
||||
} else if (diff > CesiumMath.PI) {
|
||||
destCart.longitude += CesiumMath.TWO_PI;
|
||||
}
|
||||
}
|
||||
|
||||
const scratchStartCart = new Cartographic();
|
||||
const scratchEndCart = new Cartographic();
|
||||
|
||||
function createUpdate3D(
|
||||
scene,
|
||||
duration,
|
||||
destination,
|
||||
heading,
|
||||
pitch,
|
||||
roll,
|
||||
optionAltitude,
|
||||
optionFlyOverLongitude,
|
||||
optionFlyOverLongitudeWeight,
|
||||
optionPitchAdjustHeight,
|
||||
) {
|
||||
const camera = scene.camera;
|
||||
const projection = scene.mapProjection;
|
||||
const ellipsoid = projection.ellipsoid;
|
||||
|
||||
const startCart = Cartographic.clone(
|
||||
camera.positionCartographic,
|
||||
scratchStartCart,
|
||||
);
|
||||
const startPitch = camera.pitch;
|
||||
const startHeading = adjustAngleForLERP(camera.heading, heading);
|
||||
const startRoll = adjustAngleForLERP(camera.roll, roll);
|
||||
|
||||
const destCart = ellipsoid.cartesianToCartographic(
|
||||
destination,
|
||||
scratchEndCart,
|
||||
);
|
||||
startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude);
|
||||
destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude);
|
||||
|
||||
let useLongFlight = false;
|
||||
|
||||
if (defined(optionFlyOverLongitude)) {
|
||||
const hitLon = CesiumMath.zeroToTwoPi(optionFlyOverLongitude);
|
||||
|
||||
const lonMin = Math.min(startCart.longitude, destCart.longitude);
|
||||
const lonMax = Math.max(startCart.longitude, destCart.longitude);
|
||||
|
||||
const hitInside = hitLon >= lonMin && hitLon <= lonMax;
|
||||
|
||||
if (defined(optionFlyOverLongitudeWeight)) {
|
||||
// Distance inside (0...2Pi)
|
||||
const din = Math.abs(startCart.longitude - destCart.longitude);
|
||||
// Distance outside (0...2Pi)
|
||||
const dot = CesiumMath.TWO_PI - din;
|
||||
|
||||
const hitDistance = hitInside ? din : dot;
|
||||
const offDistance = hitInside ? dot : din;
|
||||
|
||||
if (
|
||||
hitDistance < offDistance * optionFlyOverLongitudeWeight &&
|
||||
!hitInside
|
||||
) {
|
||||
useLongFlight = true;
|
||||
}
|
||||
} else if (!hitInside) {
|
||||
useLongFlight = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (useLongFlight) {
|
||||
useLongestFlight(startCart, destCart);
|
||||
} else {
|
||||
useShortestFlight(startCart, destCart);
|
||||
}
|
||||
|
||||
const heightFunction = createHeightFunction(
|
||||
camera,
|
||||
destination,
|
||||
startCart.height,
|
||||
destCart.height,
|
||||
optionAltitude,
|
||||
);
|
||||
const pitchFunction = createPitchFunction(
|
||||
startPitch,
|
||||
pitch,
|
||||
heightFunction,
|
||||
optionPitchAdjustHeight,
|
||||
);
|
||||
|
||||
// Isolate scope for update function.
|
||||
// to have local copies of vars used in lerp
|
||||
// Othervise, if you call nex
|
||||
// createUpdate3D (createAnimationTween)
|
||||
// before you played animation, variables will be overwriten.
|
||||
function isolateUpdateFunction() {
|
||||
const startLongitude = startCart.longitude;
|
||||
const destLongitude = destCart.longitude;
|
||||
const startLatitude = startCart.latitude;
|
||||
const destLatitude = destCart.latitude;
|
||||
|
||||
return function update(value) {
|
||||
const time = value.time / duration;
|
||||
|
||||
const position = Cartesian3.fromRadians(
|
||||
CesiumMath.lerp(startLongitude, destLongitude, time),
|
||||
CesiumMath.lerp(startLatitude, destLatitude, time),
|
||||
heightFunction(time),
|
||||
ellipsoid,
|
||||
);
|
||||
|
||||
camera.setView({
|
||||
destination: position,
|
||||
orientation: {
|
||||
heading: CesiumMath.lerp(startHeading, heading, time),
|
||||
pitch: pitchFunction(time),
|
||||
roll: CesiumMath.lerp(startRoll, roll, time),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
return isolateUpdateFunction();
|
||||
}
|
||||
|
||||
function createUpdate2D(
|
||||
scene,
|
||||
duration,
|
||||
destination,
|
||||
heading,
|
||||
pitch,
|
||||
roll,
|
||||
optionAltitude,
|
||||
) {
|
||||
const camera = scene.camera;
|
||||
|
||||
const start = Cartesian3.clone(camera.position, scratchStart);
|
||||
const startHeading = adjustAngleForLERP(camera.heading, heading);
|
||||
|
||||
const startHeight = camera.frustum.right - camera.frustum.left;
|
||||
const heightFunction = createHeightFunction(
|
||||
camera,
|
||||
destination,
|
||||
startHeight,
|
||||
destination.z,
|
||||
optionAltitude,
|
||||
);
|
||||
|
||||
function update(value) {
|
||||
const time = value.time / duration;
|
||||
|
||||
camera.setView({
|
||||
orientation: {
|
||||
heading: CesiumMath.lerp(startHeading, heading, time),
|
||||
},
|
||||
});
|
||||
|
||||
Cartesian2.lerp(start, destination, time, camera.position);
|
||||
|
||||
const zoom = heightFunction(time);
|
||||
|
||||
const frustum = camera.frustum;
|
||||
const ratio = frustum.top / frustum.right;
|
||||
|
||||
const incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
|
||||
frustum.right += incrementAmount;
|
||||
frustum.left -= incrementAmount;
|
||||
frustum.top = ratio * frustum.right;
|
||||
frustum.bottom = -frustum.top;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
const scratchCartographic = new Cartographic();
|
||||
const scratchDestination = new Cartesian3();
|
||||
|
||||
function emptyFlight(complete, cancel) {
|
||||
return {
|
||||
startObject: {},
|
||||
stopObject: {},
|
||||
duration: 0.0,
|
||||
complete: complete,
|
||||
cancel: cancel,
|
||||
};
|
||||
}
|
||||
|
||||
function wrapCallback(controller, cb) {
|
||||
function wrapped() {
|
||||
if (typeof cb === "function") {
|
||||
cb();
|
||||
}
|
||||
|
||||
controller.enableInputs = true;
|
||||
}
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
CameraFlightPath.createTween = function (scene, options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
let destination = options.destination;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(scene)) {
|
||||
throw new DeveloperError("scene is required.");
|
||||
}
|
||||
if (!defined(destination)) {
|
||||
throw new DeveloperError("destination is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
const mode = scene.mode;
|
||||
|
||||
if (mode === SceneMode.MORPHING) {
|
||||
return emptyFlight();
|
||||
}
|
||||
|
||||
const convert = options.convert ?? true;
|
||||
const projection = scene.mapProjection;
|
||||
const ellipsoid = projection.ellipsoid;
|
||||
const maximumHeight = options.maximumHeight;
|
||||
const flyOverLongitude = options.flyOverLongitude;
|
||||
const flyOverLongitudeWeight = options.flyOverLongitudeWeight;
|
||||
const pitchAdjustHeight = options.pitchAdjustHeight;
|
||||
let easingFunction = options.easingFunction;
|
||||
|
||||
if (convert && mode !== SceneMode.SCENE3D) {
|
||||
ellipsoid.cartesianToCartographic(destination, scratchCartographic);
|
||||
destination = projection.project(scratchCartographic, scratchDestination);
|
||||
}
|
||||
|
||||
const camera = scene.camera;
|
||||
const transform = options.endTransform;
|
||||
if (defined(transform)) {
|
||||
camera._setTransform(transform);
|
||||
}
|
||||
|
||||
let duration = options.duration;
|
||||
if (!defined(duration)) {
|
||||
duration =
|
||||
Math.ceil(Cartesian3.distance(camera.position, destination) / 1000000.0) +
|
||||
2.0;
|
||||
duration = Math.min(duration, 3.0);
|
||||
}
|
||||
|
||||
const heading = options.heading ?? 0.0;
|
||||
const pitch = options.pitch ?? -CesiumMath.PI_OVER_TWO;
|
||||
const roll = options.roll ?? 0.0;
|
||||
|
||||
const controller = scene.screenSpaceCameraController;
|
||||
controller.enableInputs = false;
|
||||
|
||||
const complete = wrapCallback(controller, options.complete);
|
||||
const cancel = wrapCallback(controller, options.cancel);
|
||||
|
||||
const frustum = camera.frustum;
|
||||
|
||||
let empty = scene.mode === SceneMode.SCENE2D;
|
||||
empty =
|
||||
empty &&
|
||||
Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6);
|
||||
empty =
|
||||
empty &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom),
|
||||
destination.z,
|
||||
CesiumMath.EPSILON6,
|
||||
);
|
||||
|
||||
empty =
|
||||
empty ||
|
||||
(scene.mode !== SceneMode.SCENE2D &&
|
||||
Cartesian3.equalsEpsilon(
|
||||
destination,
|
||||
camera.position,
|
||||
CesiumMath.EPSILON10,
|
||||
));
|
||||
|
||||
empty =
|
||||
empty &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
CesiumMath.negativePiToPi(heading),
|
||||
CesiumMath.negativePiToPi(camera.heading),
|
||||
CesiumMath.EPSILON10,
|
||||
) &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
CesiumMath.negativePiToPi(pitch),
|
||||
CesiumMath.negativePiToPi(camera.pitch),
|
||||
CesiumMath.EPSILON10,
|
||||
) &&
|
||||
CesiumMath.equalsEpsilon(
|
||||
CesiumMath.negativePiToPi(roll),
|
||||
CesiumMath.negativePiToPi(camera.roll),
|
||||
CesiumMath.EPSILON10,
|
||||
);
|
||||
|
||||
if (empty) {
|
||||
return emptyFlight(complete, cancel);
|
||||
}
|
||||
|
||||
const updateFunctions = new Array(4);
|
||||
updateFunctions[SceneMode.SCENE2D] = createUpdate2D;
|
||||
updateFunctions[SceneMode.SCENE3D] = createUpdate3D;
|
||||
updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV;
|
||||
|
||||
if (duration <= 0.0) {
|
||||
const newOnComplete = function () {
|
||||
const update = updateFunctions[mode](
|
||||
scene,
|
||||
1.0,
|
||||
destination,
|
||||
heading,
|
||||
pitch,
|
||||
roll,
|
||||
maximumHeight,
|
||||
flyOverLongitude,
|
||||
flyOverLongitudeWeight,
|
||||
pitchAdjustHeight,
|
||||
);
|
||||
update({ time: 1.0 });
|
||||
|
||||
if (typeof complete === "function") {
|
||||
complete();
|
||||
}
|
||||
};
|
||||
return emptyFlight(newOnComplete, cancel);
|
||||
}
|
||||
|
||||
const update = updateFunctions[mode](
|
||||
scene,
|
||||
duration,
|
||||
destination,
|
||||
heading,
|
||||
pitch,
|
||||
roll,
|
||||
maximumHeight,
|
||||
flyOverLongitude,
|
||||
flyOverLongitudeWeight,
|
||||
pitchAdjustHeight,
|
||||
);
|
||||
|
||||
if (!defined(easingFunction)) {
|
||||
const startHeight = camera.positionCartographic.height;
|
||||
const endHeight =
|
||||
mode === SceneMode.SCENE3D
|
||||
? ellipsoid.cartesianToCartographic(destination).height
|
||||
: destination.z;
|
||||
|
||||
if (startHeight > endHeight && startHeight > 11500.0) {
|
||||
easingFunction = EasingFunction.CUBIC_OUT;
|
||||
} else {
|
||||
easingFunction = EasingFunction.QUINTIC_IN_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
duration: duration,
|
||||
easingFunction: easingFunction,
|
||||
startObject: {
|
||||
time: 0.0,
|
||||
},
|
||||
stopObject: {
|
||||
time: duration,
|
||||
},
|
||||
update: update,
|
||||
complete: complete,
|
||||
cancel: cancel,
|
||||
};
|
||||
};
|
||||
export default CameraFlightPath;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
|
||||
/**
|
||||
* Simple abstraction for a group. This class exists to make the metadata API
|
||||
* more consistent, i.e. metadata can be accessed via
|
||||
* <code>content.group.metadata</code> much like tile metadata is accessed as
|
||||
* <code>tile.metadata</code>.
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {GroupMetadata} options.metadata The metadata associated with this group.
|
||||
*
|
||||
* @alias Cesium3DContentGroup
|
||||
* @constructor
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
function Cesium3DContentGroup(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options.metadata", options.metadata);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._metadata = options.metadata;
|
||||
}
|
||||
|
||||
Object.defineProperties(Cesium3DContentGroup.prototype, {
|
||||
/**
|
||||
* Get the metadata for this group
|
||||
*
|
||||
* @memberof Cesium3DContentGroup.prototype
|
||||
*
|
||||
* @type {GroupMetadata}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
metadata: {
|
||||
get: function () {
|
||||
return this._metadata;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default Cesium3DContentGroup;
|
||||
+2528
File diff suppressed because it is too large
Load Diff
+1132
File diff suppressed because it is too large
Load Diff
+57
@@ -0,0 +1,57 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Defines how per-feature colors set from the Cesium API or declarative styling blend with the source colors from
|
||||
* the original feature, e.g. glTF material or per-point color in the tile.
|
||||
* <p>
|
||||
* When <code>REPLACE</code> or <code>MIX</code> are used and the source color is a glTF material, the technique must assign the
|
||||
* <code>_3DTILESDIFFUSE</code> semantic to the diffuse color parameter. Otherwise only <code>HIGHLIGHT</code> is supported.
|
||||
* </p>
|
||||
* <p>
|
||||
* A feature whose color evaluates to white (1.0, 1.0, 1.0) is always rendered without color blending, regardless of the
|
||||
* tileset's color blend mode.
|
||||
* </p>
|
||||
* <pre><code>
|
||||
* "techniques": {
|
||||
* "technique0": {
|
||||
* "parameters": {
|
||||
* "diffuse": {
|
||||
* "semantic": "_3DTILESDIFFUSE",
|
||||
* "type": 35666
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const Cesium3DTileColorBlendMode = {
|
||||
/**
|
||||
* Multiplies the source color by the feature color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
HIGHLIGHT: 0,
|
||||
|
||||
/**
|
||||
* Replaces the source color with the feature color.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
REPLACE: 1,
|
||||
|
||||
/**
|
||||
* Blends the source color and feature color together.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
MIX: 2,
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTileColorBlendMode);
|
||||
|
||||
export default Cesium3DTileColorBlendMode;
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
// @ts-check
|
||||
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
|
||||
/** @import Cartesian3 from "../Core/Cartesian3.js"; */
|
||||
/** @import Cesium3DContentGroup from "./Cesium3DContentGroup.js"; */
|
||||
/** @import Cesium3DTile from "./Cesium3DTile.js"; */
|
||||
/** @import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js"; */
|
||||
/** @import Cesium3DTileFeature from "./Cesium3DTileFeature.js"; */
|
||||
/** @import Cesium3DTileStyle from "./Cesium3DTileStyle.js"; */
|
||||
/** @import Cesium3DTileset from "./Cesium3DTileset.js"; */
|
||||
/** @import Color from "../Core/Color.js"; */
|
||||
/** @import FrameState from "./FrameState.js"; */
|
||||
/** @import ImplicitMetadataView from "./ImplicitMetadataView.js"; */
|
||||
/** @import Ray from "../Core/Ray.js"; */
|
||||
|
||||
/**
|
||||
* The content of a tile in a {@link Cesium3DTileset}.
|
||||
* <p>
|
||||
* Derived classes of this interface provide access to individual features in the tile.
|
||||
* Access derived objects through {@link Cesium3DTile#content}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This type describes an interface and is not intended to be instantiated directly.
|
||||
* </p>
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
class Cesium3DTileContent {
|
||||
constructor() {
|
||||
/**
|
||||
* Gets or sets if any feature's property changed. Used to
|
||||
* optimized applying a style when a feature's property changed.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* @protected
|
||||
* @ignore
|
||||
*/
|
||||
this.featurePropertiesDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of features in the tile.
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
featuresLength;
|
||||
|
||||
/**
|
||||
* Gets the number of points in the tile.
|
||||
* <p>
|
||||
* Only applicable for tiles with Point Cloud content. This is different than {@link Cesium3DTileContent#featuresLength} which
|
||||
* equals the number of groups of points as distinguished by the <code>BATCH_ID</code> feature table semantic.
|
||||
* </p>
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/PointCloud#batched-points}
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
pointsLength;
|
||||
|
||||
/**
|
||||
* Gets the number of triangles in the tile.
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
trianglesLength;
|
||||
|
||||
/**
|
||||
* Gets the tile's geometry memory in bytes.
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
geometryByteLength;
|
||||
|
||||
/**
|
||||
* Gets the tile's texture memory in bytes.
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
texturesByteLength;
|
||||
|
||||
/**
|
||||
* Gets the amount of memory used by the batch table textures and any binary
|
||||
* metadata properties not accounted for in geometryByteLength or
|
||||
* texturesByteLength
|
||||
*
|
||||
*
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
batchTableByteLength;
|
||||
|
||||
/**
|
||||
* Gets the array of {@link Cesium3DTileContent} objects for contents that contain other contents, such as composite tiles. The inner contents may in turn have inner contents, such as a composite tile that contains a composite tile.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Composite|Composite specification}
|
||||
*
|
||||
*
|
||||
* @type {Array<*>}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
innerContents;
|
||||
|
||||
/**
|
||||
* Returns true when the tile's content is ready to render; otherwise false
|
||||
*
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
ready;
|
||||
|
||||
/**
|
||||
* Gets the tileset for this tile.
|
||||
*
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
tileset;
|
||||
|
||||
/**
|
||||
* Gets the tile containing this content.
|
||||
*
|
||||
*
|
||||
* @type {Cesium3DTile}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
tile;
|
||||
|
||||
/**
|
||||
* Gets the url of the tile's content.
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @constant
|
||||
*/
|
||||
url;
|
||||
|
||||
/**
|
||||
* Gets the batch table for this content.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @type {Cesium3DTileBatchTable}
|
||||
* @readonly
|
||||
* @constant
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
batchTable;
|
||||
|
||||
/**
|
||||
* Gets the metadata for this content, whether it is available explicitly or via
|
||||
* implicit tiling. If there is no metadata, this property should be undefined.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @type {ImplicitMetadataView|undefined}
|
||||
*
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
metadata;
|
||||
|
||||
/**
|
||||
* Gets the group for this content if the content has metadata (3D Tiles 1.1) or
|
||||
* if it uses the <code>3DTILES_metadata</code> extension. If neither are present,
|
||||
* this property should be undefined.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @type {Cesium3DContentGroup|undefined}
|
||||
*
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
group;
|
||||
|
||||
/**
|
||||
* Returns whether the feature has this property.
|
||||
*
|
||||
* @param {number} batchId The batchId for the feature.
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {boolean} <code>true</code> if the feature has this property; otherwise, <code>false</code>.
|
||||
*/
|
||||
hasProperty(batchId, name) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Cesium3DTileFeature} object for the feature with the
|
||||
* given <code>batchId</code>. This object is used to get and modify the
|
||||
* feature's properties.
|
||||
* <p>
|
||||
* Features in a tile are ordered by <code>batchId</code>, an index used to retrieve their metadata from the batch table.
|
||||
* </p>
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/BatchTable}.
|
||||
*
|
||||
* @param {number} batchId The batchId for the feature.
|
||||
* @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object.
|
||||
*
|
||||
* @exception {DeveloperError} batchId must be between zero and {@link Cesium3DTileContent#featuresLength} - 1.
|
||||
*/
|
||||
getFeature(batchId) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@link Cesium3DTileset#debugColorizeTiles} changes.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @param {boolean} enabled Whether to enable or disable debug settings.
|
||||
* @param {Color} color Debug color.
|
||||
* @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object.
|
||||
|
||||
* @private
|
||||
*/
|
||||
applyDebugSettings(enabled, color) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a style to the content
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @param {Cesium3DTileStyle} style The style.
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
applyStyle(style) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the tile during tileset traversal to get the draw commands needed to render this content.
|
||||
* When the tile's content is in the PROCESSING state, this creates WebGL resources to ultimately
|
||||
* move to the READY state.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @param {Cesium3DTileset} tileset The tileset containing this tile.
|
||||
* @param {FrameState} frameState The frame state.
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
update(tileset, frameState) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates.
|
||||
*
|
||||
* @param {Ray} ray The ray to test for intersection.
|
||||
* @param {FrameState} frameState The frame state.
|
||||
* @param {Cartesian3|undefined} [result] The intersection or <code>undefined</code> if none was found.
|
||||
* @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
pick(ray, frameState, result) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
||||
*
|
||||
* @see Cesium3DTileContent#destroy
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isDestroyed() {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* This is used to implement the <code>Cesium3DTileContent</code> interface, but is
|
||||
* not part of the public Cesium API.
|
||||
* </p>
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
* @example
|
||||
* content = content && content.destroy();
|
||||
*
|
||||
* @see Cesium3DTileContent#isDestroyed
|
||||
*
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
DeveloperError.throwInstantiationError();
|
||||
}
|
||||
}
|
||||
|
||||
export default Cesium3DTileContent;
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import Composite3DTileContent from "./Composite3DTileContent.js";
|
||||
import Geometry3DTileContent from "./Geometry3DTileContent.js";
|
||||
import Implicit3DTileContent from "./Implicit3DTileContent.js";
|
||||
import Model3DTileContent from "./Model/Model3DTileContent.js";
|
||||
import Tileset3DTileContent from "./Tileset3DTileContent.js";
|
||||
import Vector3DTileContent from "./Vector3DTileContent.js";
|
||||
import VectorGltf3DTileContent from "./VectorGltf3DTileContent.js";
|
||||
import GaussianSplat3DTileContent from "./GaussianSplat3DTileContent.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
|
||||
/**
|
||||
* Maps a tile's magic field in its header to a new content object for the tile's payload.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileContentFactory = {
|
||||
b3dm: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return Model3DTileContent.fromB3dm(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
pnts: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return Model3DTileContent.fromPnts(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
i3dm: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return Model3DTileContent.fromI3dm(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
cmpt: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
// Send in the factory in order to avoid a cyclical dependency
|
||||
return Composite3DTileContent.fromTileType(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
Cesium3DTileContentFactory,
|
||||
);
|
||||
},
|
||||
externalTileset: function (tileset, tile, resource, json) {
|
||||
return Tileset3DTileContent.fromJson(tileset, tile, resource, json);
|
||||
},
|
||||
geom: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return new Geometry3DTileContent(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
vctr: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return new Vector3DTileContent(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
subt: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
return Implicit3DTileContent.fromSubtreeJson(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
undefined,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
);
|
||||
},
|
||||
subtreeJson: function (tileset, tile, resource, json) {
|
||||
return Implicit3DTileContent.fromSubtreeJson(tileset, tile, resource, json);
|
||||
},
|
||||
glb: function (tileset, tile, resource, arrayBuffer, byteOffset) {
|
||||
const arrayBufferByteLength = arrayBuffer.byteLength;
|
||||
if (arrayBufferByteLength < 12) {
|
||||
throw new RuntimeError("Invalid glb content");
|
||||
}
|
||||
const dataView = new DataView(arrayBuffer, byteOffset);
|
||||
const byteLength = dataView.getUint32(8, true);
|
||||
const glb = new Uint8Array(arrayBuffer, byteOffset, byteLength);
|
||||
|
||||
if (
|
||||
GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt(tileset)
|
||||
) {
|
||||
return GaussianSplat3DTileContent.fromGltf(tileset, tile, resource, glb);
|
||||
}
|
||||
|
||||
// @deprecated CESIUM_mesh_vector to be removed after v1.142 release.
|
||||
if (
|
||||
tileset.hasExtension("3DTILES_content_gltf_vector") ||
|
||||
tileset.isGltfExtensionUsed("CESIUM_mesh_vector")
|
||||
) {
|
||||
return VectorGltf3DTileContent.fromGltf(tileset, tile, resource, glb);
|
||||
}
|
||||
|
||||
return Model3DTileContent.fromGltf(tileset, tile, resource, glb);
|
||||
},
|
||||
gltf: function (tileset, tile, resource, json) {
|
||||
if (
|
||||
GaussianSplat3DTileContent.tilesetRequiresGaussianSplattingExt(tileset)
|
||||
) {
|
||||
return GaussianSplat3DTileContent.fromGltf(tileset, tile, resource, json);
|
||||
}
|
||||
|
||||
// @deprecated CESIUM_mesh_vector to be removed after v1.142 release.
|
||||
if (
|
||||
tileset.hasExtension("3DTILES_content_gltf_vector") ||
|
||||
tileset.isGltfExtensionUsed("CESIUM_mesh_vector")
|
||||
) {
|
||||
return VectorGltf3DTileContent.fromGltf(tileset, tile, resource, json);
|
||||
}
|
||||
|
||||
return Model3DTileContent.fromGltf(tileset, tile, resource, json);
|
||||
},
|
||||
geoJson: function (tileset, tile, resource, json) {
|
||||
return Model3DTileContent.fromGeoJson(tileset, tile, resource, json);
|
||||
},
|
||||
};
|
||||
export default Cesium3DTileContentFactory;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileContentState = {
|
||||
UNLOADED: 0, // Has never been requested
|
||||
LOADING: 1, // Is waiting on a pending request
|
||||
PROCESSING: 2, // Request received. Contents are being processed for rendering. Depending on the content, it might make its own requests for external data.
|
||||
READY: 3, // Ready to render.
|
||||
EXPIRED: 4, // Is expired and will be unloaded once new content is loaded.
|
||||
FAILED: 5, // Request failed.
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTileContentState);
|
||||
|
||||
export default Cesium3DTileContentState;
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* An enum to indicate the different types of {@link Cesium3DTileContent}.
|
||||
* For binary files, the enum value is the magic number of the binary file
|
||||
* unless otherwise noted. For JSON files, the enum value is a unique name
|
||||
* for internal use.
|
||||
*
|
||||
* @enum {string}
|
||||
* @see Cesium3DTileContent
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileContentType = {
|
||||
/**
|
||||
* A Batched 3D Model. This is a binary format with
|
||||
* magic number <code>b3dm</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
BATCHED_3D_MODEL: "b3dm",
|
||||
/**
|
||||
* An Instanced 3D Model. This is a binary format with magic number
|
||||
* <code>i3dm</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
INSTANCED_3D_MODEL: "i3dm",
|
||||
/**
|
||||
* A Composite model. This is a binary format with magic number
|
||||
* <code>cmpt</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
COMPOSITE: "cmpt",
|
||||
/**
|
||||
* A Point Cloud model. This is a binary format with magic number
|
||||
* <code>pnts</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
POINT_CLOUD: "pnts",
|
||||
/**
|
||||
* Vector tiles. This is a binary format with magic number
|
||||
* <code>vctr</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
VECTOR: "vctr",
|
||||
/**
|
||||
* Geometry tiles. This is a binary format with magic number
|
||||
* <code>geom</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
GEOMETRY: "geom",
|
||||
/**
|
||||
* A glTF model in JSON + external BIN form. This is treated
|
||||
* as a JSON format.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
GLTF: "gltf",
|
||||
/**
|
||||
* The binary form of a glTF file. Internally, the magic number is
|
||||
* changed from <code>glTF</code> to <code>glb</code> to distinguish it from
|
||||
* the JSON glTF format.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
GLTF_BINARY: "glb",
|
||||
/**
|
||||
* For implicit tiling, availability bitstreams are stored in binary subtree files.
|
||||
* The magic number is <code>subt</code>
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
IMPLICIT_SUBTREE: "subt",
|
||||
/**
|
||||
* For implicit tiling. Subtrees can also be represented as JSON files.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
IMPLICIT_SUBTREE_JSON: "subtreeJson",
|
||||
/**
|
||||
* Contents can reference another tileset.json to use
|
||||
* as an external tileset. This is a JSON-based format.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
EXTERNAL_TILESET: "externalTileset",
|
||||
/**
|
||||
* Multiple contents are handled separately from the other content types
|
||||
* due to differences in request scheduling.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
MULTIPLE_CONTENT: "multipleContent",
|
||||
/**
|
||||
* GeoJSON content for <code>MAXAR_content_geojson</code> extension.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
GEOJSON: "geoJson",
|
||||
/**
|
||||
* Binary voxel content for <code>3DTILES_content_voxels</code> extension.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
VOXEL_BINARY: "voxl",
|
||||
/**
|
||||
* Binary voxel content for <code>3DTILES_content_voxels</code> extension.
|
||||
*
|
||||
* @type {string}
|
||||
* @constant
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
VOXEL_JSON: "voxelJson",
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a content is one of the supported binary formats. Otherwise,
|
||||
* the caller can assume a JSON format.
|
||||
* @param {Cesium3DTileContentType} contentType The content type of the content payload.
|
||||
* @return {boolean} <code>true</code> if the content type is a binary format, or <code>false</code> if the content type is a JSON format.
|
||||
* @private
|
||||
*/
|
||||
Cesium3DTileContentType.isBinaryFormat = function (contentType) {
|
||||
switch (contentType) {
|
||||
case Cesium3DTileContentType.BATCHED_3D_MODEL:
|
||||
case Cesium3DTileContentType.INSTANCED_3D_MODEL:
|
||||
case Cesium3DTileContentType.COMPOSITE:
|
||||
case Cesium3DTileContentType.POINT_CLOUD:
|
||||
case Cesium3DTileContentType.VECTOR:
|
||||
case Cesium3DTileContentType.GEOMETRY:
|
||||
case Cesium3DTileContentType.IMPLICIT_SUBTREE:
|
||||
case Cesium3DTileContentType.VOXEL_BINARY:
|
||||
case Cesium3DTileContentType.GLTF_BINARY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTileContentType);
|
||||
|
||||
export default Cesium3DTileContentType;
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
import Color from "../Core/Color.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/** @import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js"; */
|
||||
/** @import Cesium3DTileContent from "./Cesium3DTileContent.js"; */
|
||||
/** @import Cesium3DTileset from "./Cesium3DTileset.js"; */
|
||||
|
||||
/**
|
||||
* A feature of a {@link Cesium3DTileset}.
|
||||
* <p>
|
||||
* Provides access to a feature's properties stored in the tile's batch table, as well
|
||||
* as the ability to show/hide a feature and change its highlight color via
|
||||
* {@link Cesium3DTileFeature#show} and {@link Cesium3DTileFeature#color}, respectively.
|
||||
* </p>
|
||||
* <p>
|
||||
* Modifications to a <code>Cesium3DTileFeature</code> object have the lifetime of the tile's
|
||||
* content. If the tile's content is unloaded, e.g., due to it going out of view and needing
|
||||
* to free space in the cache for visible tiles, listen to the {@link Cesium3DTileset#tileUnload} event to save any
|
||||
* modifications. Also listen to the {@link Cesium3DTileset#tileVisible} event to reapply any modifications.
|
||||
* </p>
|
||||
* <p>
|
||||
* Do not construct this directly. Access it through {@link Cesium3DTileContent#getFeature}
|
||||
* or picking using {@link Scene#pick}.
|
||||
* </p>
|
||||
*
|
||||
* @example
|
||||
* // On mouse over, display all the properties for a feature in the console log.
|
||||
* handler.setInputAction(function(movement) {
|
||||
* const feature = scene.pick(movement.endPosition);
|
||||
* if (feature instanceof Cesium.Cesium3DTileFeature) {
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId}: ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
* }
|
||||
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||
*/
|
||||
class Cesium3DTileFeature {
|
||||
/**
|
||||
* @param {Cesium3DTileContent} content
|
||||
* @param {number} batchId
|
||||
*/
|
||||
constructor(content, batchId) {
|
||||
this._content = content;
|
||||
this._batchId = batchId;
|
||||
this._color = undefined; // for calling getColor
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets if the feature will be shown. This is set for all features
|
||||
* when a style's show is evaluated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
get show() {
|
||||
return this._content.batchTable.getShow(this._batchId);
|
||||
}
|
||||
|
||||
set show(value) {
|
||||
this._content.batchTable.setShow(this._batchId, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the highlight color multiplied with the feature's color. When
|
||||
* this is white, the feature's color is not changed. This is set for all features
|
||||
* when a style's color is evaluated.
|
||||
*
|
||||
* @type {Color}
|
||||
*
|
||||
* @default {@link Color.WHITE}
|
||||
*/
|
||||
get color() {
|
||||
if (!defined(this._color)) {
|
||||
this._color = new Color();
|
||||
}
|
||||
return this._content.batchTable.getColor(this._batchId, this._color);
|
||||
}
|
||||
|
||||
set color(value) {
|
||||
this._content.batchTable.setColor(this._batchId, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a typed array containing the ECEF positions of the polyline.
|
||||
* Returns undefined if {@link Cesium3DTileset#vectorKeepDecodedPositions} is false
|
||||
* or the feature is not a polyline in a vector tile.
|
||||
*
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*
|
||||
* @type {Float64Array}
|
||||
*/
|
||||
get polylinePositions() {
|
||||
if (!defined(this._content.getPolylinePositions)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._content.getPolylinePositions(this._batchId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of the tile containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileContent}
|
||||
*
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get content() {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
get tileset() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* All objects returned by {@link Scene#pick} have a <code>primitive</code> property. This returns
|
||||
* the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
get primitive() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feature ID associated with this feature. For 3D Tiles 1.0, the
|
||||
* batch ID is returned. For EXT_mesh_features, this is the feature ID from
|
||||
* the selected feature ID set.
|
||||
*
|
||||
* @type {number}
|
||||
*
|
||||
* @readonly
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
get featureId() {
|
||||
return this._batchId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
get pickId() {
|
||||
return this._content.batchTable.getPickColor(this._batchId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature contains this property. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {boolean} Whether the feature contains this property.
|
||||
*/
|
||||
hasProperty(name) {
|
||||
return this._content.batchTable.hasProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of property IDs for the feature. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string[]} [results] An array into which to store the results.
|
||||
* @returns {string[]} The IDs of the feature's properties.
|
||||
*/
|
||||
getPropertyIds(results) {
|
||||
return this._content.batchTable.getPropertyIds(this._batchId, results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
*
|
||||
* @example
|
||||
* // Display all the properties for a feature in the console log.
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId}: ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
*/
|
||||
getProperty(name) {
|
||||
return this._content.batchTable.getProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the feature's property with the given name, examining all
|
||||
* the metadata from 3D Tiles 1.0 formats, the EXT_structural_metadata and legacy
|
||||
* EXT_feature_metadata glTF extensions, and the metadata present either in the
|
||||
* tileset JSON (3D Tiles 1.1) or in the 3DTILES_metadata 3D Tiles extension.
|
||||
* Metadata is checked against name from most specific to most general and the
|
||||
* first match is returned. Metadata is checked in this order:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Batch table (structural metadata) property by semantic</li>
|
||||
* <li>Batch table (structural metadata) property by property ID</li>
|
||||
* <li>Content metadata property by semantic</li>
|
||||
* <li>Content metadata property by property</li>
|
||||
* <li>Tile metadata property by semantic</li>
|
||||
* <li>Tile metadata property by property ID</li>
|
||||
* <li>Subtree metadata property by semantic</li>
|
||||
* <li>Subtree metadata property by property ID</li>
|
||||
* <li>Group metadata property by semantic</li>
|
||||
* <li>Group metadata property by property ID</li>
|
||||
* <li>Tileset metadata property by semantic</li>
|
||||
* <li>Tileset metadata property by property ID</li>
|
||||
* <li>Otherwise, return undefined</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* For 3D Tiles Next details, see the {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata|3DTILES_metadata Extension}
|
||||
* for 3D Tiles, as well as the {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata|EXT_structural_metadata Extension}
|
||||
* for glTF. For the legacy glTF extension, see {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata|EXT_feature_metadata Extension}
|
||||
* </p>
|
||||
*
|
||||
* @param {Cesium3DTileContent} content The content for accessing the metadata
|
||||
* @param {number} batchId The batch ID (or feature ID) of the feature to get a property for
|
||||
* @param {string} name The semantic or property ID of the feature. Semantics are checked before property IDs in each granularity of metadata.
|
||||
* @privateParam {Cesium3DTileBatchTable} [batchTable] Batch table in which to look up the feature property. If unspecified, `content.batchTable` is used.
|
||||
* @return {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
*
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
static getPropertyInherited(
|
||||
content,
|
||||
batchId,
|
||||
name,
|
||||
batchTable = content.batchTable,
|
||||
) {
|
||||
if (defined(batchTable)) {
|
||||
if (batchTable.hasPropertyBySemantic(batchId, name)) {
|
||||
return batchTable.getPropertyBySemantic(batchId, name);
|
||||
}
|
||||
|
||||
if (batchTable.hasProperty(batchId, name)) {
|
||||
return batchTable.getProperty(batchId, name);
|
||||
}
|
||||
}
|
||||
|
||||
const contentMetadata = content.metadata;
|
||||
if (defined(contentMetadata)) {
|
||||
if (contentMetadata.hasPropertyBySemantic(name)) {
|
||||
return contentMetadata.getPropertyBySemantic(name);
|
||||
}
|
||||
|
||||
if (contentMetadata.hasProperty(name)) {
|
||||
return contentMetadata.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
const tile = content.tile;
|
||||
const tileMetadata = tile.metadata;
|
||||
if (defined(tileMetadata)) {
|
||||
if (tileMetadata.hasPropertyBySemantic(name)) {
|
||||
return tileMetadata.getPropertyBySemantic(name);
|
||||
}
|
||||
|
||||
if (tileMetadata.hasProperty(name)) {
|
||||
return tileMetadata.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
let subtreeMetadata;
|
||||
if (defined(tile.implicitSubtree)) {
|
||||
subtreeMetadata = tile.implicitSubtree.metadata;
|
||||
}
|
||||
|
||||
if (defined(subtreeMetadata)) {
|
||||
if (subtreeMetadata.hasPropertyBySemantic(name)) {
|
||||
return subtreeMetadata.getPropertyBySemantic(name);
|
||||
}
|
||||
|
||||
if (subtreeMetadata.hasProperty(name)) {
|
||||
return subtreeMetadata.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
const groupMetadata = defined(content.group)
|
||||
? content.group.metadata
|
||||
: undefined;
|
||||
if (defined(groupMetadata)) {
|
||||
if (groupMetadata.hasPropertyBySemantic(name)) {
|
||||
return groupMetadata.getPropertyBySemantic(name);
|
||||
}
|
||||
|
||||
if (groupMetadata.hasProperty(name)) {
|
||||
return groupMetadata.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
const tilesetMetadata = content.tileset.metadata;
|
||||
if (defined(tilesetMetadata)) {
|
||||
if (tilesetMetadata.hasPropertyBySemantic(name)) {
|
||||
return tilesetMetadata.getPropertyBySemantic(name);
|
||||
}
|
||||
|
||||
if (tilesetMetadata.hasProperty(name)) {
|
||||
return tilesetMetadata.getProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name.
|
||||
* If the feature is contained within a tileset that has metadata (3D Tiles 1.1)
|
||||
* or uses the <code>3DTILES_metadata</code> extension, tileset, group and tile
|
||||
* metadata is inherited.
|
||||
* <p>
|
||||
* To resolve name conflicts, this method resolves names from most specific to
|
||||
* least specific by metadata granularity in the order: feature, tile, group,
|
||||
* tileset. Within each granularity, semantics are resolved first, then other
|
||||
* properties.
|
||||
* </p>
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
* @private
|
||||
*/
|
||||
getPropertyInherited(name) {
|
||||
return Cesium3DTileFeature.getPropertyInherited(
|
||||
this._content,
|
||||
this._batchId,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the feature's property with the given name.
|
||||
* <p>
|
||||
* If a property with the given name doesn't exist, it is created.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
*
|
||||
* @exception {DeveloperError} Inherited batch table hierarchy property is read only.
|
||||
*
|
||||
* @example
|
||||
* const height = feature.getProperty('Height'); // e.g., the height of a building
|
||||
*
|
||||
* @example
|
||||
* const name = 'clicked';
|
||||
* if (feature.getProperty(name)) {
|
||||
* console.log('already clicked');
|
||||
* } else {
|
||||
* feature.setProperty(name, true);
|
||||
* console.log('first click');
|
||||
* }
|
||||
*/
|
||||
setProperty(name, value) {
|
||||
this._content.batchTable.setProperty(this._batchId, name, value);
|
||||
|
||||
// PERFORMANCE_IDEA: Probably overkill, but maybe only mark the tile dirty if the
|
||||
// property is in one of the style's expressions or - if it can be done quickly -
|
||||
// if the new property value changed the result of an expression.
|
||||
this._content.featurePropertiesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class name equals <code>className</code>. Unlike {@link Cesium3DTileFeature#isClass}
|
||||
* this function only checks the feature's exact class and not inherited classes.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class name equals <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isExactClass(className) {
|
||||
return this._content.batchTable.isExactClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class or any inherited classes are named <code>className</code>.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class or inherited classes are named <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isClass(className) {
|
||||
return this._content.batchTable.isClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the feature's class name.
|
||||
* <p>
|
||||
* This function returns <code>undefined</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @returns {string} The feature's class name.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getExactClassName() {
|
||||
return this._content.batchTable.getExactClassName(this._batchId);
|
||||
}
|
||||
}
|
||||
|
||||
export default Cesium3DTileFeature;
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTileFeatureTable(featureTableJson, featureTableBinary) {
|
||||
this.json = featureTableJson;
|
||||
this.buffer = featureTableBinary;
|
||||
this._cachedTypedArrays = {};
|
||||
this.featuresLength = 0;
|
||||
}
|
||||
|
||||
function getTypedArrayFromBinary(
|
||||
featureTable,
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
count,
|
||||
byteOffset,
|
||||
) {
|
||||
const cachedTypedArrays = featureTable._cachedTypedArrays;
|
||||
let typedArray = cachedTypedArrays[semantic];
|
||||
if (!defined(typedArray)) {
|
||||
typedArray = ComponentDatatype.createArrayBufferView(
|
||||
componentType,
|
||||
featureTable.buffer.buffer,
|
||||
featureTable.buffer.byteOffset + byteOffset,
|
||||
count * componentLength,
|
||||
);
|
||||
cachedTypedArrays[semantic] = typedArray;
|
||||
}
|
||||
return typedArray;
|
||||
}
|
||||
|
||||
function getTypedArrayFromArray(featureTable, semantic, componentType, array) {
|
||||
const cachedTypedArrays = featureTable._cachedTypedArrays;
|
||||
let typedArray = cachedTypedArrays[semantic];
|
||||
if (!defined(typedArray)) {
|
||||
typedArray = ComponentDatatype.createTypedArray(componentType, array);
|
||||
cachedTypedArrays[semantic] = typedArray;
|
||||
}
|
||||
return typedArray;
|
||||
}
|
||||
|
||||
Cesium3DTileFeatureTable.prototype.getGlobalProperty = function (
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
) {
|
||||
const jsonValue = this.json[semantic];
|
||||
if (!defined(jsonValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (defined(jsonValue.byteOffset)) {
|
||||
componentType = componentType ?? ComponentDatatype.UNSIGNED_INT;
|
||||
componentLength = componentLength ?? 1;
|
||||
return getTypedArrayFromBinary(
|
||||
this,
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
1,
|
||||
jsonValue.byteOffset,
|
||||
);
|
||||
}
|
||||
|
||||
return jsonValue;
|
||||
};
|
||||
|
||||
Cesium3DTileFeatureTable.prototype.hasProperty = function (semantic) {
|
||||
return defined(this.json[semantic]);
|
||||
};
|
||||
|
||||
Cesium3DTileFeatureTable.prototype.getPropertyArray = function (
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
) {
|
||||
const jsonValue = this.json[semantic];
|
||||
if (!defined(jsonValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (defined(jsonValue.byteOffset)) {
|
||||
if (defined(jsonValue.componentType)) {
|
||||
componentType = ComponentDatatype.fromName(jsonValue.componentType);
|
||||
}
|
||||
return getTypedArrayFromBinary(
|
||||
this,
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
this.featuresLength,
|
||||
jsonValue.byteOffset,
|
||||
);
|
||||
}
|
||||
|
||||
return getTypedArrayFromArray(this, semantic, componentType, jsonValue);
|
||||
};
|
||||
|
||||
Cesium3DTileFeatureTable.prototype.getProperty = function (
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
featureId,
|
||||
result,
|
||||
) {
|
||||
const jsonValue = this.json[semantic];
|
||||
if (!defined(jsonValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typedArray = this.getPropertyArray(
|
||||
semantic,
|
||||
componentType,
|
||||
componentLength,
|
||||
);
|
||||
|
||||
if (componentLength === 1) {
|
||||
return typedArray[featureId];
|
||||
}
|
||||
|
||||
for (let i = 0; i < componentLength; ++i) {
|
||||
result[i] = typedArray[componentLength * featureId + i];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
export default Cesium3DTileFeatureTable;
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Hint defining optimization support for a 3D tile
|
||||
*
|
||||
* @enum {number}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileOptimizationHint = {
|
||||
NOT_COMPUTED: -1,
|
||||
USE_OPTIMIZATION: 1,
|
||||
SKIP_OPTIMIZATION: 0,
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTileOptimizationHint);
|
||||
|
||||
export default Cesium3DTileOptimizationHint;
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
|
||||
import TileBoundingRegion from "./TileBoundingRegion.js";
|
||||
import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
|
||||
|
||||
/**
|
||||
* Utility functions for computing optimization hints for a {@link Cesium3DTileset}.
|
||||
*
|
||||
* @namespace Cesium3DTileOptimizations
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileOptimizations = {};
|
||||
|
||||
const scratchAxis = new Cartesian3();
|
||||
|
||||
/**
|
||||
* Evaluates support for the childrenWithinParent optimization. This is used to more tightly cull tilesets if
|
||||
* children bounds are fully contained within the parent. Currently, support for the optimization only works for
|
||||
* oriented bounding boxes, so both the child and parent tile must be either a {@link TileOrientedBoundingBox} or
|
||||
* {@link TileBoundingRegion}. The purpose of this check is to prevent use of a culling optimization when the child
|
||||
* bounds exceed those of the parent. If the child bounds are greater, it is more likely that the optimization will
|
||||
* waste CPU cycles. Bounding spheres are not supported for the reason that the child bounds can very often be
|
||||
* partially outside of the parent bounds.
|
||||
*
|
||||
* @param {Cesium3DTile} tile The tile to check.
|
||||
* @returns {boolean} Whether the childrenWithinParent optimization is supported.
|
||||
*/
|
||||
Cesium3DTileOptimizations.checkChildrenWithinParent = function (tile) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("tile", tile);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const children = tile.children;
|
||||
const length = children.length;
|
||||
|
||||
// Check if the parent has an oriented bounding box.
|
||||
const boundingVolume = tile.boundingVolume;
|
||||
if (
|
||||
boundingVolume instanceof TileOrientedBoundingBox ||
|
||||
boundingVolume instanceof TileBoundingRegion
|
||||
) {
|
||||
const orientedBoundingBox = boundingVolume._orientedBoundingBox;
|
||||
tile._optimChildrenWithinParent =
|
||||
Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const child = children[i];
|
||||
|
||||
// Check if the child has an oriented bounding box.
|
||||
const childBoundingVolume = child.boundingVolume;
|
||||
if (
|
||||
!(
|
||||
childBoundingVolume instanceof TileOrientedBoundingBox ||
|
||||
childBoundingVolume instanceof TileBoundingRegion
|
||||
)
|
||||
) {
|
||||
// Do not support if the parent and child both do not have oriented bounding boxes.
|
||||
tile._optimChildrenWithinParent =
|
||||
Cesium3DTileOptimizationHint.SKIP_OPTIMIZATION;
|
||||
break;
|
||||
}
|
||||
|
||||
const childOrientedBoundingBox = childBoundingVolume._orientedBoundingBox;
|
||||
|
||||
// Compute the axis from the parent to the child.
|
||||
const axis = Cartesian3.subtract(
|
||||
childOrientedBoundingBox.center,
|
||||
orientedBoundingBox.center,
|
||||
scratchAxis,
|
||||
);
|
||||
const axisLength = Cartesian3.magnitude(axis);
|
||||
Cartesian3.divideByScalar(axis, axisLength, axis);
|
||||
|
||||
// Project the bounding box of the parent onto the axis. Because the axis is a ray from the parent
|
||||
// to the child, the projection parameterized along the ray will be (+/- proj1).
|
||||
const proj1 =
|
||||
Math.abs(orientedBoundingBox.halfAxes[0] * axis.x) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[1] * axis.y) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[2] * axis.z) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[3] * axis.x) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[4] * axis.y) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[5] * axis.z) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[6] * axis.x) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[7] * axis.y) +
|
||||
Math.abs(orientedBoundingBox.halfAxes[8] * axis.z);
|
||||
|
||||
// Project the bounding box of the child onto the axis. Because the axis is a ray from the parent
|
||||
// to the child, the projection parameterized along the ray will be (+/- proj2) + axis.length.
|
||||
const proj2 =
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[0] * axis.x) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[1] * axis.y) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[2] * axis.z) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[3] * axis.x) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[4] * axis.y) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[5] * axis.z) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[6] * axis.x) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[7] * axis.y) +
|
||||
Math.abs(childOrientedBoundingBox.halfAxes[8] * axis.z);
|
||||
|
||||
// If the child extends the parent's bounds, the optimization is not valid and we skip it.
|
||||
if (proj1 <= proj2 + axisLength) {
|
||||
tile._optimChildrenWithinParent =
|
||||
Cesium3DTileOptimizationHint.SKIP_OPTIMIZATION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
tile._optimChildrenWithinParent ===
|
||||
Cesium3DTileOptimizationHint.USE_OPTIMIZATION
|
||||
);
|
||||
};
|
||||
export default Cesium3DTileOptimizations;
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* The pass in which a 3D Tileset is updated.
|
||||
*
|
||||
* @enum {number}
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTilePass = {
|
||||
RENDER: 0,
|
||||
PICK: 1,
|
||||
SHADOW: 2,
|
||||
PRELOAD: 3,
|
||||
PRELOAD_FLIGHT: 4,
|
||||
REQUEST_RENDER_MODE_DEFER_CHECK: 5,
|
||||
MOST_DETAILED_PRELOAD: 6,
|
||||
MOST_DETAILED_PICK: 7,
|
||||
NUMBER_OF_PASSES: 8,
|
||||
};
|
||||
|
||||
const passOptions = new Array(Cesium3DTilePass.NUMBER_OF_PASSES);
|
||||
|
||||
passOptions[Cesium3DTilePass.RENDER] = Object.freeze({
|
||||
pass: Cesium3DTilePass.RENDER,
|
||||
isRender: true,
|
||||
requestTiles: true,
|
||||
ignoreCommands: false,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.PICK] = Object.freeze({
|
||||
pass: Cesium3DTilePass.PICK,
|
||||
isRender: false,
|
||||
requestTiles: false,
|
||||
ignoreCommands: false,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.SHADOW] = Object.freeze({
|
||||
pass: Cesium3DTilePass.SHADOW,
|
||||
isRender: false,
|
||||
requestTiles: true,
|
||||
ignoreCommands: false,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.PRELOAD] = Object.freeze({
|
||||
pass: Cesium3DTilePass.PRELOAD,
|
||||
isRender: false,
|
||||
requestTiles: true,
|
||||
ignoreCommands: true,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.PRELOAD_FLIGHT] = Object.freeze({
|
||||
pass: Cesium3DTilePass.PRELOAD_FLIGHT,
|
||||
isRender: false,
|
||||
requestTiles: true,
|
||||
ignoreCommands: true,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK] = Object.freeze({
|
||||
pass: Cesium3DTilePass.REQUEST_RENDER_MODE_DEFER_CHECK,
|
||||
isRender: false,
|
||||
requestTiles: true,
|
||||
ignoreCommands: true,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.MOST_DETAILED_PRELOAD] = Object.freeze({
|
||||
pass: Cesium3DTilePass.MOST_DETAILED_PRELOAD,
|
||||
isRender: false,
|
||||
requestTiles: true,
|
||||
ignoreCommands: true,
|
||||
});
|
||||
|
||||
passOptions[Cesium3DTilePass.MOST_DETAILED_PICK] = Object.freeze({
|
||||
pass: Cesium3DTilePass.MOST_DETAILED_PICK,
|
||||
isRender: false,
|
||||
requestTiles: false,
|
||||
ignoreCommands: false,
|
||||
});
|
||||
|
||||
Cesium3DTilePass.getPassOptions = function (pass) {
|
||||
return passOptions[pass];
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTilePass);
|
||||
|
||||
export default Cesium3DTilePass;
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import Check from "../Core/Check.js";
|
||||
|
||||
/**
|
||||
* The state for a 3D Tiles update pass.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
*/
|
||||
function Cesium3DTilePassState(options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options", options);
|
||||
Check.typeOf.number("options.pass", options.pass);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
/**
|
||||
* The pass.
|
||||
*
|
||||
* @type {Cesium3DTilePass}
|
||||
*/
|
||||
this.pass = options.pass;
|
||||
|
||||
/**
|
||||
* An array of rendering commands to use instead of {@link FrameState.commandList} for the current pass.
|
||||
*
|
||||
* @type {DrawCommand[]}
|
||||
*/
|
||||
this.commandList = options.commandList;
|
||||
|
||||
/**
|
||||
* A camera to use instead of {@link FrameState.camera} for the current pass.
|
||||
*
|
||||
* @type {Camera}
|
||||
*/
|
||||
this.camera = options.camera;
|
||||
|
||||
/**
|
||||
* A culling volume to use instead of {@link FrameState.cullingVolume} for the current pass.
|
||||
*
|
||||
* @type {CullingVolume}
|
||||
*/
|
||||
this.cullingVolume = options.cullingVolume;
|
||||
|
||||
/**
|
||||
* A read-only property that indicates whether the pass is ready, i.e. all tiles needed by the pass are loaded.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @default false
|
||||
*/
|
||||
this.ready = false;
|
||||
}
|
||||
export default Cesium3DTilePassState;
|
||||
+778
@@ -0,0 +1,778 @@
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
|
||||
import createBillboardPointCallback from "./createBillboardPointCallback.js";
|
||||
|
||||
/** @import Billboard from "./Billboard.js"; */
|
||||
/** @import Cesium3DTileContent from "./Cesium3DTileContent.js"; */
|
||||
/** @import Cesium3DTileset from "./Cesium3DTileset.js"; */
|
||||
/** @import DistanceDisplayCondition from "../Core/DistanceDisplayCondition.js"; */
|
||||
/** @import HorizontalOrigin from "./HorizontalOrigin.js"; */
|
||||
/** @import Label from "./Label.js"; */
|
||||
/** @import NearFarScalar from "../Core/NearFarScalar.js"; */
|
||||
/** @import Polyline from "./Polyline.js"; */
|
||||
/** @import VerticalOrigin from "./VerticalOrigin.js"; */
|
||||
|
||||
/** @ignore */
|
||||
const scratchCartographic = new Cartographic();
|
||||
|
||||
/**
|
||||
* A point feature of a {@link Cesium3DTileset}.
|
||||
* <p>
|
||||
* Provides access to a feature's properties stored in the tile's batch table, as well
|
||||
* as the ability to show/hide a feature and change its point properties
|
||||
* </p>
|
||||
* <p>
|
||||
* Modifications to a <code>Cesium3DTilePointFeature</code> object have the lifetime of the tile's
|
||||
* content. If the tile's content is unloaded, e.g., due to it going out of view and needing
|
||||
* to free space in the cache for visible tiles, listen to the {@link Cesium3DTileset#tileUnload} event to save any
|
||||
* modifications. Also listen to the {@link Cesium3DTileset#tileVisible} event to reapply any modifications.
|
||||
* </p>
|
||||
* <p>
|
||||
* Do not construct this directly. Access it through {@link Cesium3DTileContent#getFeature}
|
||||
* or picking using {@link Scene#pick} and {@link Scene#pickPosition}.
|
||||
* </p>
|
||||
*
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*
|
||||
* @example
|
||||
* // On mouse over, display all the properties for a feature in the console log.
|
||||
* handler.setInputAction(function(movement) {
|
||||
* const feature = scene.pick(movement.endPosition);
|
||||
* if (feature instanceof Cesium.Cesium3DTilePointFeature) {
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId}: ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
* }
|
||||
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||
*/
|
||||
class Cesium3DTilePointFeature {
|
||||
static defaultColor = Color.WHITE;
|
||||
static defaultPointOutlineColor = Color.BLACK;
|
||||
static defaultPointOutlineWidth = 0.0;
|
||||
static defaultPointSize = 8.0;
|
||||
|
||||
/**
|
||||
* @param {Cesium3DTileContent} content
|
||||
* @param {number} batchId
|
||||
* @param {Billboard} billboard
|
||||
* @param {Label} label
|
||||
* @param {Polyline} polyline
|
||||
*/
|
||||
constructor(content, batchId, billboard, label, polyline) {
|
||||
this._content = content;
|
||||
this._billboard = billboard;
|
||||
this._label = label;
|
||||
this._polyline = polyline;
|
||||
|
||||
this._batchId = batchId;
|
||||
this._billboardImage = undefined;
|
||||
this._billboardColor = undefined;
|
||||
this._billboardOutlineColor = undefined;
|
||||
this._billboardOutlineWidth = undefined;
|
||||
this._billboardSize = undefined;
|
||||
this._pointSize = undefined;
|
||||
this._color = undefined;
|
||||
this._pointSize = undefined;
|
||||
this._pointOutlineColor = undefined;
|
||||
this._pointOutlineWidth = undefined;
|
||||
this._heightOffset = undefined;
|
||||
|
||||
this._pickIds = new Array(3);
|
||||
|
||||
setBillboardImage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets if the feature will be shown. This is set for all features
|
||||
* when a style's show is evaluated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
get show() {
|
||||
return this._label.show;
|
||||
}
|
||||
|
||||
set show(value) {
|
||||
this._label.show = value;
|
||||
this._billboard.show = value;
|
||||
this._polyline.show = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the color of the point of this feature.
|
||||
* <p>
|
||||
* Only applied when <code>image</code> is <code>undefined</code>.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get color() {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
set color(value) {
|
||||
this._color = Color.clone(value, this._color);
|
||||
setBillboardImage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the point size of this feature.
|
||||
* <p>
|
||||
* Only applied when <code>image</code> is <code>undefined</code>.
|
||||
* </p>
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get pointSize() {
|
||||
return this._pointSize;
|
||||
}
|
||||
|
||||
set pointSize(value) {
|
||||
this._pointSize = value;
|
||||
setBillboardImage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the point outline color of this feature.
|
||||
* <p>
|
||||
* Only applied when <code>image</code> is <code>undefined</code>.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get pointOutlineColor() {
|
||||
return this._pointOutlineColor;
|
||||
}
|
||||
|
||||
set pointOutlineColor(value) {
|
||||
this._pointOutlineColor = Color.clone(value, this._pointOutlineColor);
|
||||
setBillboardImage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the point outline width in pixels of this feature.
|
||||
* <p>
|
||||
* Only applied when <code>image</code> is <code>undefined</code>.
|
||||
* </p>
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get pointOutlineWidth() {
|
||||
return this._pointOutlineWidth;
|
||||
}
|
||||
|
||||
set pointOutlineWidth(value) {
|
||||
this._pointOutlineWidth = value;
|
||||
setBillboardImage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the label color of this feature.
|
||||
* <p>
|
||||
* The color will be applied to the label if <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get labelColor() {
|
||||
return this._label.fillColor;
|
||||
}
|
||||
|
||||
set labelColor(value) {
|
||||
this._label.fillColor = value;
|
||||
this._polyline.show = this._label.show && value.alpha > 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the label outline color of this feature.
|
||||
* <p>
|
||||
* The outline color will be applied to the label if <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get labelOutlineColor() {
|
||||
return this._label.outlineColor;
|
||||
}
|
||||
|
||||
set labelOutlineColor(value) {
|
||||
this._label.outlineColor = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the outline width in pixels of this feature.
|
||||
* <p>
|
||||
* The outline width will be applied to the point if <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get labelOutlineWidth() {
|
||||
return this._label.outlineWidth;
|
||||
}
|
||||
|
||||
set labelOutlineWidth(value) {
|
||||
this._label.outlineWidth = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the font of this feature.
|
||||
* <p>
|
||||
* Only applied when the <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get font() {
|
||||
return this._label.font;
|
||||
}
|
||||
|
||||
set font(value) {
|
||||
this._label.font = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the fill and outline style of this feature.
|
||||
* <p>
|
||||
* Only applied when <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {LabelStyle}
|
||||
*/
|
||||
get labelStyle() {
|
||||
return this._label.style;
|
||||
}
|
||||
|
||||
set labelStyle(value) {
|
||||
this._label.style = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the text for this feature.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get labelText() {
|
||||
return this._label.text;
|
||||
}
|
||||
|
||||
set labelText(value) {
|
||||
if (!defined(value)) {
|
||||
value = "";
|
||||
}
|
||||
this._label.text = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the background color of the text for this feature.
|
||||
* <p>
|
||||
* Only applied when <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get backgroundColor() {
|
||||
return this._label.backgroundColor;
|
||||
}
|
||||
|
||||
set backgroundColor(value) {
|
||||
this._label.backgroundColor = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the background padding of the text for this feature.
|
||||
* <p>
|
||||
* Only applied when <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {Cartesian2}
|
||||
*/
|
||||
get backgroundPadding() {
|
||||
return this._label.backgroundPadding;
|
||||
}
|
||||
|
||||
set backgroundPadding(value) {
|
||||
this._label.backgroundPadding = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether to display the background of the text for this feature.
|
||||
* <p>
|
||||
* Only applied when <code>labelText</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get backgroundEnabled() {
|
||||
return this._label.showBackground;
|
||||
}
|
||||
|
||||
set backgroundEnabled(value) {
|
||||
this._label.showBackground = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the near and far scaling properties for this feature.
|
||||
*
|
||||
* @type {NearFarScalar}
|
||||
*/
|
||||
get scaleByDistance() {
|
||||
return this._label.scaleByDistance;
|
||||
}
|
||||
|
||||
set scaleByDistance(value) {
|
||||
this._label.scaleByDistance = value;
|
||||
this._billboard.scaleByDistance = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the near and far translucency properties for this feature.
|
||||
*
|
||||
* @type {NearFarScalar}
|
||||
*/
|
||||
get translucencyByDistance() {
|
||||
return this._label.translucencyByDistance;
|
||||
}
|
||||
|
||||
set translucencyByDistance(value) {
|
||||
this._label.translucencyByDistance = value;
|
||||
this._billboard.translucencyByDistance = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the condition specifying at what distance from the camera that this feature will be displayed.
|
||||
*
|
||||
* @type {DistanceDisplayCondition}
|
||||
*/
|
||||
get distanceDisplayCondition() {
|
||||
return this._label.distanceDisplayCondition;
|
||||
}
|
||||
|
||||
set distanceDisplayCondition(value) {
|
||||
this._label.distanceDisplayCondition = value;
|
||||
this._polyline.distanceDisplayCondition = value;
|
||||
this._billboard.distanceDisplayCondition = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the height offset in meters of this feature.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get heightOffset() {
|
||||
return this._heightOffset;
|
||||
}
|
||||
|
||||
set heightOffset(value) {
|
||||
const offset = this._heightOffset ?? 0.0;
|
||||
|
||||
const ellipsoid = this._content.tileset.ellipsoid;
|
||||
const cart = ellipsoid.cartesianToCartographic(
|
||||
this._billboard.position,
|
||||
scratchCartographic,
|
||||
);
|
||||
cart.height = cart.height - offset + value;
|
||||
const newPosition = ellipsoid.cartographicToCartesian(cart);
|
||||
|
||||
this._billboard.position = newPosition;
|
||||
this._label.position = this._billboard.position;
|
||||
this._polyline.positions = [this._polyline.positions[0], newPosition];
|
||||
|
||||
this._heightOffset = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets whether the anchor line is displayed.
|
||||
* <p>
|
||||
* Only applied when <code>heightOffset</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get anchorLineEnabled() {
|
||||
return this._polyline.show;
|
||||
}
|
||||
|
||||
set anchorLineEnabled(value) {
|
||||
this._polyline.show = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the color for the anchor line.
|
||||
* <p>
|
||||
* Only applied when <code>heightOffset</code> is defined.
|
||||
* </p>
|
||||
*
|
||||
* @type {Color}
|
||||
*/
|
||||
get anchorLineColor() {
|
||||
return this._polyline.material.uniforms.color;
|
||||
}
|
||||
|
||||
set anchorLineColor(value) {
|
||||
this._polyline.material.uniforms.color = Color.clone(
|
||||
value,
|
||||
this._polyline.material.uniforms.color,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the image of this feature.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get image() {
|
||||
return this._billboardImage;
|
||||
}
|
||||
|
||||
set image(value) {
|
||||
const imageChanged = this._billboardImage !== value;
|
||||
this._billboardImage = value;
|
||||
if (imageChanged) {
|
||||
setBillboardImage(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the distance where depth testing will be disabled.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get disableDepthTestDistance() {
|
||||
return this._label.disableDepthTestDistance;
|
||||
}
|
||||
|
||||
set disableDepthTestDistance(value) {
|
||||
this._label.disableDepthTestDistance = value;
|
||||
this._billboard.disableDepthTestDistance = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the horizontal origin of this point, which determines if the point is
|
||||
* to the left, center, or right of its anchor position.
|
||||
*
|
||||
* @type {HorizontalOrigin}
|
||||
*/
|
||||
get horizontalOrigin() {
|
||||
return this._billboard.horizontalOrigin;
|
||||
}
|
||||
|
||||
set horizontalOrigin(value) {
|
||||
this._billboard.horizontalOrigin = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the vertical origin of this point, which determines if the point is
|
||||
* to the bottom, center, or top of its anchor position.
|
||||
*
|
||||
* @type {VerticalOrigin}
|
||||
*/
|
||||
get verticalOrigin() {
|
||||
return this._billboard.verticalOrigin;
|
||||
}
|
||||
|
||||
set verticalOrigin(value) {
|
||||
this._billboard.verticalOrigin = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the horizontal origin of this point's text, which determines if the point's text is
|
||||
* to the left, center, or right of its anchor position.
|
||||
*
|
||||
* @type {HorizontalOrigin}
|
||||
*/
|
||||
get labelHorizontalOrigin() {
|
||||
return this._label.horizontalOrigin;
|
||||
}
|
||||
|
||||
set labelHorizontalOrigin(value) {
|
||||
this._label.horizontalOrigin = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or sets the vertical origin of this point's text, which determines if the point's text is
|
||||
* to the bottom, center, top, or baseline of it's anchor point.
|
||||
*
|
||||
* @type {VerticalOrigin}
|
||||
*/
|
||||
get labelVerticalOrigin() {
|
||||
return this._label.verticalOrigin;
|
||||
}
|
||||
|
||||
set labelVerticalOrigin(value) {
|
||||
this._label.verticalOrigin = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of the tile containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileContent}
|
||||
*
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get content() {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
get tileset() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* All objects returned by {@link Scene#pick} have a <code>primitive</code> property. This returns
|
||||
* the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
get primitive() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
get pickIds() {
|
||||
const ids = this._pickIds;
|
||||
ids[0] = this._billboard.pickId;
|
||||
ids[1] = this._label.pickId;
|
||||
ids[2] = this._polyline.pickId;
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature contains this property. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {boolean} Whether the feature contains this property.
|
||||
*/
|
||||
hasProperty(name) {
|
||||
return this._content.batchTable.hasProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of property IDs for the feature. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string[]} [results] An array into which to store the results.
|
||||
* @returns {string[]} The IDs of the feature's properties.
|
||||
*/
|
||||
getPropertyIds(results) {
|
||||
return this._content.batchTable.getPropertyIds(this._batchId, results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
*
|
||||
* @example
|
||||
* // Display all the properties for a feature in the console log.
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId} : ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
*/
|
||||
getProperty(name) {
|
||||
return this._content.batchTable.getProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name.
|
||||
* If the feature is contained within a tileset that has metadata (3D Tiles 1.1)
|
||||
* or uses the <code>3DTILES_metadata</code> extension, tileset, group and tile metadata is
|
||||
* inherited.
|
||||
* <p>
|
||||
* To resolve name conflicts, this method resolves names from most specific to
|
||||
* least specific by metadata granularity in the order: feature, tile, group,
|
||||
* tileset. Within each granularity, semantics are resolved first, then other
|
||||
* properties.
|
||||
* </p>
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
getPropertyInherited(name) {
|
||||
return Cesium3DTileFeature.getPropertyInherited(
|
||||
this._content,
|
||||
this._batchId,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the feature's property with the given name.
|
||||
* <p>
|
||||
* If a property with the given name doesn't exist, it is created.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
*
|
||||
* @exception {DeveloperError} Inherited batch table hierarchy property is read only.
|
||||
*
|
||||
* @example
|
||||
* const height = feature.getProperty('Height'); // e.g., the height of a building
|
||||
*
|
||||
* @example
|
||||
* const name = 'clicked';
|
||||
* if (feature.getProperty(name)) {
|
||||
* console.log('already clicked');
|
||||
* } else {
|
||||
* feature.setProperty(name, true);
|
||||
* console.log('first click');
|
||||
* }
|
||||
*/
|
||||
setProperty(name, value) {
|
||||
this._content.batchTable.setProperty(this._batchId, name, value);
|
||||
|
||||
// PERFORMANCE_IDEA: Probably overkill, but maybe only mark the tile dirty if the
|
||||
// property is in one of the style's expressions or - if it can be done quickly -
|
||||
// if the new property value changed the result of an expression.
|
||||
this._content.featurePropertiesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class name equals <code>className</code>. Unlike {@link Cesium3DTilePointFeature#isClass}
|
||||
* this function only checks the feature's exact class and not inherited classes.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class name equals <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isExactClass(className) {
|
||||
return this._content.batchTable.isExactClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class or any inherited classes are named <code>className</code>.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class or inherited classes are named <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isClass(className) {
|
||||
return this._content.batchTable.isClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the feature's class name.
|
||||
* <p>
|
||||
* This function returns <code>undefined</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @returns {string} The feature's class name.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getExactClassName() {
|
||||
return this._content.batchTable.getExactClassName(this._batchId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Cesium3DTilePointFeature} feature
|
||||
* @ignore
|
||||
*/
|
||||
function setBillboardImage(feature) {
|
||||
const b = feature._billboard;
|
||||
if (defined(feature._billboardImage) && feature._billboardImage !== b.image) {
|
||||
b.image = feature._billboardImage;
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined(feature._billboardImage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newColor = feature._color ?? Cesium3DTilePointFeature.defaultColor;
|
||||
const newOutlineColor =
|
||||
feature._pointOutlineColor ??
|
||||
Cesium3DTilePointFeature.defaultPointOutlineColor;
|
||||
const newOutlineWidth =
|
||||
feature._pointOutlineWidth ??
|
||||
Cesium3DTilePointFeature.defaultPointOutlineWidth;
|
||||
const newPointSize =
|
||||
feature._pointSize ?? Cesium3DTilePointFeature.defaultPointSize;
|
||||
|
||||
const currentColor = feature._billboardColor;
|
||||
const currentOutlineColor = feature._billboardOutlineColor;
|
||||
const currentOutlineWidth = feature._billboardOutlineWidth;
|
||||
const currentPointSize = feature._billboardSize;
|
||||
|
||||
if (
|
||||
Color.equals(newColor, currentColor) &&
|
||||
Color.equals(newOutlineColor, currentOutlineColor) &&
|
||||
newOutlineWidth === currentOutlineWidth &&
|
||||
newPointSize === currentPointSize
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
feature._billboardColor = Color.clone(newColor, feature._billboardColor);
|
||||
feature._billboardOutlineColor = Color.clone(
|
||||
newOutlineColor,
|
||||
feature._billboardOutlineColor,
|
||||
);
|
||||
feature._billboardOutlineWidth = newOutlineWidth;
|
||||
feature._billboardSize = newPointSize;
|
||||
|
||||
const centerAlpha = newColor.alpha;
|
||||
const cssColor = newColor.toCssColorString();
|
||||
const cssOutlineColor = newOutlineColor.toCssColorString();
|
||||
const textureId = JSON.stringify([
|
||||
cssColor,
|
||||
newPointSize,
|
||||
cssOutlineColor,
|
||||
newOutlineWidth,
|
||||
]);
|
||||
|
||||
b.setImage(
|
||||
textureId,
|
||||
createBillboardPointCallback(
|
||||
centerAlpha,
|
||||
cssColor,
|
||||
cssOutlineColor,
|
||||
newOutlineWidth,
|
||||
newPointSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export default Cesium3DTilePointFeature;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* The refinement approach for a tile.
|
||||
* <p>
|
||||
* See the {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification#refinement|Refinement}
|
||||
* in the 3D Tiles spec.
|
||||
* </p>
|
||||
*
|
||||
* @enum {number}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const Cesium3DTileRefine = {
|
||||
/**
|
||||
* Render this tile and, if it doesn't meet the screen space error, also refine to its children.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ADD: 0,
|
||||
|
||||
/**
|
||||
* Render this tile or, if it doesn't meet the screen space error, refine to its descendants instead.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
REPLACE: 1,
|
||||
};
|
||||
|
||||
Object.freeze(Cesium3DTileRefine);
|
||||
|
||||
export default Cesium3DTileRefine;
|
||||
+1497
File diff suppressed because it is too large
Load Diff
+78
@@ -0,0 +1,78 @@
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTileStyleEngine() {
|
||||
this._style = undefined; // The style provided by the user
|
||||
this._styleDirty = false; // true when the style is reassigned
|
||||
this._lastStyleTime = 0; // The "time" when the last style was assigned
|
||||
}
|
||||
|
||||
Object.defineProperties(Cesium3DTileStyleEngine.prototype, {
|
||||
style: {
|
||||
get: function () {
|
||||
return this._style;
|
||||
},
|
||||
set: function (value) {
|
||||
if (value === this._style) {
|
||||
return;
|
||||
}
|
||||
this._style = value;
|
||||
this._styleDirty = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Cesium3DTileStyleEngine.prototype.makeDirty = function () {
|
||||
this._styleDirty = true;
|
||||
};
|
||||
|
||||
Cesium3DTileStyleEngine.prototype.resetDirty = function () {
|
||||
this._styleDirty = false;
|
||||
};
|
||||
|
||||
Cesium3DTileStyleEngine.prototype.applyStyle = function (tileset) {
|
||||
if (!defined(tileset.root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined(this._style) && !this._style._ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styleDirty = this._styleDirty;
|
||||
|
||||
if (styleDirty) {
|
||||
// Increase "time", so the style is applied to all visible tiles
|
||||
++this._lastStyleTime;
|
||||
}
|
||||
|
||||
const lastStyleTime = this._lastStyleTime;
|
||||
const statistics = tileset._statistics;
|
||||
|
||||
// If a new style was assigned, loop through all the visible tiles; otherwise, loop through
|
||||
// only the tiles that are newly visible, i.e., they are visible this frame, but were not
|
||||
// visible last frame. In many cases, the newly selected tiles list will be short or empty.
|
||||
const tiles = styleDirty
|
||||
? tileset._selectedTiles
|
||||
: tileset._selectedTilesToStyle;
|
||||
// PERFORMANCE_IDEA: does mouse-over picking basically trash this? We need to style on
|
||||
// pick, for example, because a feature's show may be false.
|
||||
|
||||
const length = tiles.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const tile = tiles[i];
|
||||
if (tile.lastStyleTime !== lastStyleTime) {
|
||||
// Apply the style to this tile if it wasn't already applied because:
|
||||
// 1) the user assigned a new style to the tileset
|
||||
// 2) this tile is now visible, but it wasn't visible when the style was first assigned
|
||||
const content = tile.content;
|
||||
tile.lastStyleTime = lastStyleTime;
|
||||
content.applyStyle(this._style);
|
||||
statistics.numberOfFeaturesStyled += content.featuresLength;
|
||||
++statistics.numberOfTilesStyled;
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Cesium3DTileStyleEngine;
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
// @ts-check
|
||||
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import BufferPoint from "./BufferPoint.js";
|
||||
import BufferPointCollection from "./BufferPointCollection.js";
|
||||
import BufferPointMaterial from "./BufferPointMaterial.js";
|
||||
import BufferPolygon from "./BufferPolygon.js";
|
||||
import BufferPolygonCollection from "./BufferPolygonCollection.js";
|
||||
import BufferPolygonMaterial from "./BufferPolygonMaterial.js";
|
||||
import BufferPolyline from "./BufferPolyline.js";
|
||||
import BufferPolylineCollection from "./BufferPolylineCollection.js";
|
||||
import BufferPolylineMaterial from "./BufferPolylineMaterial.js";
|
||||
import Cesium3DTileFeature from "./Cesium3DTileFeature.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/** @import BufferPrimitive from "./BufferPrimitive.js"; */
|
||||
/** @import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js"; */
|
||||
/** @import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js"; */
|
||||
/** @import Cesium3DTileContent from "./Cesium3DTileContent.js"; */
|
||||
/** @import Cesium3DTileset from "./Cesium3DTileset.js"; */
|
||||
/** @import VectorGltf3DTileContent from "./VectorGltf3DTileContent.js"; */
|
||||
|
||||
const point = new BufferPoint();
|
||||
const polyline = new BufferPolyline();
|
||||
const polygon = new BufferPolygon();
|
||||
|
||||
const pointMaterial = new BufferPointMaterial();
|
||||
const polylineMaterial = new BufferPolylineMaterial();
|
||||
const polygonMaterial = new BufferPolygonMaterial();
|
||||
|
||||
/**
|
||||
* A vector feature of a {@link Cesium3DTileset}.
|
||||
* <p>
|
||||
* Provides access to a feature's properties stored in the tile's batch table, as well
|
||||
* as the ability to show/hide and style the feature
|
||||
* </p>
|
||||
* <p>
|
||||
* Modifications to a <code>Cesium3DTileVectorFeature</code> object have the lifetime of the tile's
|
||||
* content. If the tile's content is unloaded, e.g., due to it going out of view and needing
|
||||
* to free space in the cache for visible tiles, listen to the {@link Cesium3DTileset#tileUnload} event to save any
|
||||
* modifications. Also listen to the {@link Cesium3DTileset#tileVisible} event to reapply any modifications.
|
||||
* </p>
|
||||
* <p>
|
||||
* Do not construct this directly. Access it through {@link Cesium3DTileContent#getFeature}
|
||||
* or picking using {@link Scene#pick} and {@link Scene#pickPosition}.
|
||||
* </p>
|
||||
*
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*
|
||||
* @example
|
||||
* // On mouse over, display all the properties for a feature in the console log.
|
||||
* handler.setInputAction(function(movement) {
|
||||
* const feature = scene.pick(movement.endPosition);
|
||||
* if (feature instanceof Cesium.Cesium3DTileVectorFeature) {
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId}: ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
* }
|
||||
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
class Cesium3DTileVectorFeature {
|
||||
/** @private */
|
||||
_color = new Color();
|
||||
|
||||
/** @private */
|
||||
_outlineColor = new Color();
|
||||
|
||||
/**
|
||||
* @param {VectorGltf3DTileContent} content
|
||||
* @param {number} batchId
|
||||
* @param {number} [batchTableId=0]
|
||||
*/
|
||||
constructor(content, batchId, batchTableId = 0) {
|
||||
this._content = content;
|
||||
this._batchId = batchId;
|
||||
this._batchTableId = batchTableId;
|
||||
|
||||
/**
|
||||
* For each collection index N, this map returns the indices of all
|
||||
* primitive in the collection associated with this feature.
|
||||
* @type {Map<number, number[]>}
|
||||
* @private
|
||||
*/
|
||||
this._primitivesByCollection = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} collectionIndex
|
||||
* @param {number} primitiveIndex
|
||||
*/
|
||||
addPrimitiveByCollection(collectionIndex, primitiveIndex) {
|
||||
let primitiveIndices = this._primitivesByCollection.get(collectionIndex);
|
||||
if (!primitiveIndices) {
|
||||
primitiveIndices = [];
|
||||
this._primitivesByCollection.set(collectionIndex, primitiveIndices);
|
||||
}
|
||||
primitiveIndices.push(primitiveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
get show() {
|
||||
for (const prim of this._iteratePrimitives()) {
|
||||
if (prim.show) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
set show(value) {
|
||||
for (const prim of this._iteratePrimitives()) {
|
||||
prim.show = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
get color() {
|
||||
for (const material of this._iterateMaterials()) {
|
||||
return Color.clone(material.color, this._color);
|
||||
}
|
||||
return Color.clone(Color.WHITE, this._color);
|
||||
}
|
||||
|
||||
set color(value) {
|
||||
for (const material of this._iterateMaterials()) {
|
||||
Color.clone(value, material.color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @default 1
|
||||
*/
|
||||
get pointSize() {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
return material.size;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
set pointSize(value) {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
material.size = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
get pointOutlineColor() {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
return Color.clone(material.outlineColor, this._outlineColor);
|
||||
}
|
||||
return Color.clone(Color.WHITE, this._outlineColor);
|
||||
}
|
||||
|
||||
set pointOutlineColor(value) {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
Color.clone(value, material.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @default 0
|
||||
*/
|
||||
get pointOutlineWidth() {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
return material.outlineWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
set pointOutlineWidth(value) {
|
||||
for (const material of this._iteratePointMaterials()) {
|
||||
material.outlineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @default 1
|
||||
*/
|
||||
get lineWidth() {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
return material.width;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
set lineWidth(value) {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
material.width = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
get lineOutlineColor() {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
return Color.clone(material.outlineColor, this._outlineColor);
|
||||
}
|
||||
return Color.clone(Color.WHITE, this._outlineColor);
|
||||
}
|
||||
|
||||
set lineOutlineColor(value) {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
Color.clone(value, material.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @default 0
|
||||
*/
|
||||
get lineOutlineWidth() {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
return material.outlineWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
set lineOutlineWidth(value) {
|
||||
for (const material of this._iteratePolylineMaterials()) {
|
||||
material.outlineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
get polygonOutlineColor() {
|
||||
for (const material of this._iteratePolygonMaterials()) {
|
||||
return Color.clone(material.outlineColor, this._outlineColor);
|
||||
}
|
||||
return Color.clone(Color.WHITE, this._outlineColor);
|
||||
}
|
||||
|
||||
set polygonOutlineColor(value) {
|
||||
for (const material of this._iteratePolygonMaterials()) {
|
||||
Color.clone(value, material.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @default 0
|
||||
*/
|
||||
get polygonOutlineWidth() {
|
||||
for (const material of this._iteratePolygonMaterials()) {
|
||||
return material.outlineWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
set polygonOutlineWidth(value) {
|
||||
for (const material of this._iteratePolygonMaterials()) {
|
||||
material.outlineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of the tile containing the feature.
|
||||
*
|
||||
* @type {VectorGltf3DTileContent}
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
get content() {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*/
|
||||
get tileset() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* All objects returned by {@link Scene#pick} have a <code>primitive</code> property. This returns
|
||||
* the tileset containing the feature.
|
||||
*
|
||||
* @type {Cesium3DTileset}
|
||||
*/
|
||||
get primitive() {
|
||||
return this._content.tileset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the feature ID associated with this feature. Using EXT_mesh_features,
|
||||
* this is the feature ID from the selected feature ID set.
|
||||
*
|
||||
* @type {number}
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
get featureId() {
|
||||
return this._batchId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Cesium3DTileBatchTable|undefined}
|
||||
* @private
|
||||
*/
|
||||
get _batchTable() {
|
||||
return this._content.batchTables[this._batchTableId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number[]}
|
||||
* @ignore
|
||||
*/
|
||||
get pickIds() {
|
||||
const pickIds = [];
|
||||
for (const prim of this._iteratePrimitives()) {
|
||||
pickIds.push(prim._pickId);
|
||||
}
|
||||
return pickIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature contains this property. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {boolean} Whether the feature contains this property.
|
||||
*/
|
||||
hasProperty(name) {
|
||||
if (!defined(this._batchTable)) {
|
||||
return false;
|
||||
}
|
||||
return this._batchTable.hasProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of property IDs for the feature. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string[]} [results] An array into which to store the results.
|
||||
* @returns {string[]} The IDs of the feature's properties.
|
||||
*/
|
||||
getPropertyIds(results) {
|
||||
if (!defined(this._batchTable)) {
|
||||
return [];
|
||||
}
|
||||
return this._batchTable.getPropertyIds(this._batchId, results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name. This includes properties from this feature's
|
||||
* class and inherited classes when using a batch table hierarchy.
|
||||
*
|
||||
* @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_batch_table_hierarchy}
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
*
|
||||
* @example
|
||||
* // Display all the properties for a feature in the console log.
|
||||
* const propertyIds = feature.getPropertyIds();
|
||||
* const length = propertyIds.length;
|
||||
* for (let i = 0; i < length; ++i) {
|
||||
* const propertyId = propertyIds[i];
|
||||
* console.log(`{propertyId} : ${feature.getProperty(propertyId)}`);
|
||||
* }
|
||||
*/
|
||||
getProperty(name) {
|
||||
if (!defined(this._batchTable)) {
|
||||
return undefined;
|
||||
}
|
||||
return this._batchTable.getProperty(this._batchId, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the feature's property with the given name.
|
||||
* If the feature is contained within a tileset that has metadata (3D Tiles 1.1)
|
||||
* or uses the <code>3DTILES_metadata</code> extension, tileset, group and tile metadata is
|
||||
* inherited.
|
||||
* <p>
|
||||
* To resolve name conflicts, this method resolves names from most specific to
|
||||
* least specific by metadata granularity in the order: feature, tile, group,
|
||||
* tileset. Within each granularity, semantics are resolved first, then other
|
||||
* properties.
|
||||
* </p>
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the feature does not have this property.
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
getPropertyInherited(name) {
|
||||
return Cesium3DTileFeature.getPropertyInherited(
|
||||
// @ts-expect-error Requires type checking in Cesium3DTileContent.
|
||||
this._content,
|
||||
this._batchId,
|
||||
name,
|
||||
this._batchTable,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the feature's property with the given name.
|
||||
* <p>
|
||||
* If a property with the given name doesn't exist, it is created.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} name The case-sensitive name of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
*
|
||||
* @exception {DeveloperError} Inherited batch table hierarchy property is read only.
|
||||
*
|
||||
* @example
|
||||
* const height = feature.getProperty('Height'); // e.g., the height of a building
|
||||
*
|
||||
* @example
|
||||
* const name = 'clicked';
|
||||
* if (feature.getProperty(name)) {
|
||||
* console.log('already clicked');
|
||||
* } else {
|
||||
* feature.setProperty(name, true);
|
||||
* console.log('first click');
|
||||
* }
|
||||
*/
|
||||
setProperty(name, value) {
|
||||
throw new DeveloperError("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class name equals <code>className</code>. Unlike {@link Cesium3DTileVectorFeature#isClass}
|
||||
* this function only checks the feature's exact class and not inherited classes.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class name equals <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isExactClass(className) {
|
||||
if (!defined(this._batchTable)) {
|
||||
return false;
|
||||
}
|
||||
return this._batchTable.isExactClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the feature's class or any inherited classes are named <code>className</code>.
|
||||
* <p>
|
||||
* This function returns <code>false</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} className The name to check against.
|
||||
* @returns {boolean} Whether the feature's class or inherited classes are named <code>className</code>
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isClass(className) {
|
||||
if (!defined(this._batchTable)) {
|
||||
return false;
|
||||
}
|
||||
return this._batchTable.isClass(this._batchId, className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the feature's class name.
|
||||
* <p>
|
||||
* This function returns <code>undefined</code> if no batch table hierarchy is present.
|
||||
* </p>
|
||||
*
|
||||
* @returns {string} The feature's class name.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getExactClassName() {
|
||||
if (!defined(this._batchTable)) {
|
||||
return undefined;
|
||||
}
|
||||
return this._batchTable.getExactClassName(this._batchId);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// INTERNAL ITERATORS
|
||||
|
||||
/**
|
||||
* @returns {Iterable<BufferPrimitive>}
|
||||
*/
|
||||
*_iteratePrimitives() {
|
||||
yield* this._iteratePrimitivesWith(BufferPointCollection, point);
|
||||
yield* this._iteratePrimitivesWith(BufferPolylineCollection, polyline);
|
||||
yield* this._iteratePrimitivesWith(BufferPolygonCollection, polygon);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} CollectionType
|
||||
* @param {BufferPrimitive} result
|
||||
* @returns {Iterable<BufferPrimitive>}
|
||||
*/
|
||||
*_iteratePrimitivesWith(CollectionType, result) {
|
||||
const collections = this._content._collections;
|
||||
for (let i = 0; i < collections.length; i++) {
|
||||
const collection = collections[i];
|
||||
const primitiveIndices = this._primitivesByCollection.get(i);
|
||||
if (primitiveIndices && collection instanceof CollectionType) {
|
||||
for (const primitiveIndex of primitiveIndices) {
|
||||
collection.get(primitiveIndex, result);
|
||||
yield result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Iterable<BufferPrimitiveMaterial>} */
|
||||
*_iterateMaterials() {
|
||||
yield* this._iteratePointMaterials();
|
||||
yield* this._iteratePolylineMaterials();
|
||||
yield* this._iteratePolygonMaterials();
|
||||
}
|
||||
|
||||
/** @returns {Iterable<BufferPointMaterial>} */
|
||||
*_iteratePointMaterials() {
|
||||
yield* /** @type {Iterable<BufferPointMaterial>} */ (
|
||||
this._iterateMaterialsWith(BufferPointCollection, point, pointMaterial)
|
||||
);
|
||||
}
|
||||
|
||||
/** @returns {Iterable<BufferPolylineMaterial>} */
|
||||
*_iteratePolylineMaterials() {
|
||||
yield* /** @type {Iterable<BufferPolylineMaterial>} */ (
|
||||
this._iterateMaterialsWith(
|
||||
BufferPolylineCollection,
|
||||
polyline,
|
||||
polylineMaterial,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** @returns {Iterable<BufferPolygonMaterial>} */
|
||||
*_iteratePolygonMaterials() {
|
||||
yield* /** @type {Iterable<BufferPolygonMaterial>} */ (
|
||||
this._iterateMaterialsWith(
|
||||
BufferPolygonCollection,
|
||||
polygon,
|
||||
polygonMaterial,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} CollectionType
|
||||
* @param {BufferPrimitive} primitive
|
||||
* @param {BufferPrimitiveMaterial} result
|
||||
* @returns {Iterable<BufferPrimitiveMaterial>}
|
||||
*/
|
||||
*_iterateMaterialsWith(CollectionType, primitive, result) {
|
||||
for (const prim of this._iteratePrimitivesWith(CollectionType, primitive)) {
|
||||
prim.getMaterial(result);
|
||||
yield result;
|
||||
prim.setMaterial(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Cesium3DTileVectorFeature;
|
||||
+760
@@ -0,0 +1,760 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cesium3DTilesetMetadata from "./Cesium3DTilesetMetadata.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
import hasExtension from "./hasExtension.js";
|
||||
import ImplicitSubtree from "./ImplicitSubtree.js";
|
||||
import ImplicitSubtreeCache from "./ImplicitSubtreeCache.js";
|
||||
import ImplicitTileCoordinates from "./ImplicitTileCoordinates.js";
|
||||
import ImplicitTileset from "./ImplicitTileset.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import MetadataSemantic from "./MetadataSemantic.js";
|
||||
import MetadataType from "./MetadataType.js";
|
||||
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
|
||||
import preprocess3DTileContent from "./preprocess3DTileContent.js";
|
||||
import Resource from "../Core/Resource.js";
|
||||
import ResourceCache from "./ResourceCache.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
import VoxelContent from "./VoxelContent.js";
|
||||
import VoxelMetadataOrder from "./VoxelMetadataOrder.js";
|
||||
import VoxelShapeType from "./VoxelShapeType.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import Quaternion from "../Core/Quaternion.js";
|
||||
|
||||
/**
|
||||
* @typedef {object} Cesium3DTilesVoxelProvider.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the Cesium3DTilesVoxelProvider constructor
|
||||
*
|
||||
* @property {string} className The class in the tileset schema describing voxel metadata.
|
||||
* @property {string[]} names The metadata names.
|
||||
* @property {MetadataType[]} types The metadata types.
|
||||
* @property {MetadataComponentType[]} componentTypes The metadata component types.
|
||||
* @property {VoxelShapeType} shape The {@link VoxelShapeType}.
|
||||
* @property {Cartesian3} dimensions The number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
|
||||
* @property {Cartesian3} [paddingBefore=Cartesian3.ZERO] The number of padding voxels before the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
|
||||
* @property {Cartesian3} [paddingAfter=Cartesian3.ZERO] The number of padding voxels after the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
|
||||
* @property {Matrix4} [globalTransform=Matrix4.IDENTITY] A transform from local space to global space.
|
||||
* @property {Matrix4} [shapeTransform=Matrix4.IDENTITY] A transform from shape space to local space.
|
||||
* @property {Cartesian3} [minBounds] The minimum bounds.
|
||||
* @property {Cartesian3} [maxBounds] The maximum bounds.
|
||||
* @property {number[][]} [minimumValues] The metadata minimum values.
|
||||
* @property {number[][]} [maximumValues] The metadata maximum values.
|
||||
* @property {number} [maximumTileCount] The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be undefined.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset.
|
||||
* <p>
|
||||
* Implements the {@link VoxelProvider} interface.
|
||||
* </p>
|
||||
* <div class="notice">
|
||||
* This object is normally not instantiated directly, use {@link Cesium3DTilesVoxelProvider.fromUrl}.
|
||||
* </div>
|
||||
*
|
||||
* @alias Cesium3DTilesVoxelProvider
|
||||
* @constructor
|
||||
* @augments VoxelProvider
|
||||
*
|
||||
* @param {Cesium3DTilesVoxelProvider.ConstructorOptions} options An object describing initialization options
|
||||
*
|
||||
* @see Cesium3DTilesVoxelProvider.fromUrl
|
||||
* @see VoxelProvider
|
||||
* @see VoxelPrimitive
|
||||
* @see VoxelShapeType
|
||||
*
|
||||
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
function Cesium3DTilesVoxelProvider(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
const {
|
||||
className,
|
||||
names,
|
||||
types,
|
||||
componentTypes,
|
||||
shape,
|
||||
dimensions,
|
||||
paddingBefore = Cartesian3.ZERO.clone(),
|
||||
paddingAfter = Cartesian3.ZERO.clone(),
|
||||
globalTransform = Matrix4.IDENTITY.clone(),
|
||||
shapeTransform = Matrix4.IDENTITY.clone(),
|
||||
minBounds,
|
||||
maxBounds,
|
||||
minimumValues,
|
||||
maximumValues,
|
||||
maximumTileCount,
|
||||
} = options;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.string("className", className);
|
||||
Check.typeOf.object("names", names);
|
||||
Check.typeOf.object("types", types);
|
||||
Check.typeOf.object("componentTypes", componentTypes);
|
||||
Check.typeOf.string("shape", shape);
|
||||
Check.typeOf.object("dimensions", dimensions);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._shapeTransform = shapeTransform;
|
||||
this._globalTransform = globalTransform;
|
||||
this._shape = shape;
|
||||
this._minBounds = minBounds;
|
||||
this._maxBounds = maxBounds;
|
||||
this._dimensions = dimensions;
|
||||
this._paddingBefore = paddingBefore;
|
||||
this._paddingAfter = paddingAfter;
|
||||
this._className = className;
|
||||
this._names = names;
|
||||
this._types = types;
|
||||
this._componentTypes = componentTypes;
|
||||
this._metadataOrder =
|
||||
shape === VoxelShapeType.ELLIPSOID
|
||||
? VoxelMetadataOrder.Z_UP
|
||||
: VoxelMetadataOrder.Y_UP;
|
||||
this._minimumValues = minimumValues;
|
||||
this._maximumValues = maximumValues;
|
||||
this._maximumTileCount = maximumTileCount;
|
||||
this._availableLevels = undefined;
|
||||
this._implicitTileset = undefined;
|
||||
this._subtreeCache = new ImplicitSubtreeCache();
|
||||
}
|
||||
|
||||
Object.defineProperties(Cesium3DTilesVoxelProvider.prototype, {
|
||||
/**
|
||||
* A transform from local space to global space.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
* @readonly
|
||||
*/
|
||||
globalTransform: {
|
||||
get: function () {
|
||||
return this._globalTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A transform from shape space to local space.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
* @readonly
|
||||
*/
|
||||
shapeTransform: {
|
||||
get: function () {
|
||||
return this._shapeTransform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the {@link VoxelShapeType}
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {VoxelShapeType}
|
||||
* @readonly
|
||||
*/
|
||||
shape: {
|
||||
get: function () {
|
||||
return this._shape;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the minimum bounds.
|
||||
* If undefined, the shape's default minimum bounds will be used instead.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Cartesian3|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
minBounds: {
|
||||
get: function () {
|
||||
return this._minBounds;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the maximum bounds.
|
||||
* If undefined, the shape's default maximum bounds will be used instead.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Cartesian3|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maxBounds: {
|
||||
get: function () {
|
||||
return this._maxBounds;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Cartesian3}
|
||||
* @readonly
|
||||
*/
|
||||
dimensions: {
|
||||
get: function () {
|
||||
return this._dimensions;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of padding voxels before the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Cartesian3}
|
||||
* @default Cartesian3.ZERO
|
||||
* @readonly
|
||||
*/
|
||||
paddingBefore: {
|
||||
get: function () {
|
||||
return this._paddingBefore;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the number of padding voxels after the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {Cartesian3}
|
||||
* @default Cartesian3.ZERO
|
||||
* @readonly
|
||||
*/
|
||||
paddingAfter: {
|
||||
get: function () {
|
||||
return this._paddingAfter;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The metadata class for this tileset.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
className: {
|
||||
get: function () {
|
||||
return this._className;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata names.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {string[]}
|
||||
* @readonly
|
||||
*/
|
||||
names: {
|
||||
get: function () {
|
||||
return this._names;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata types.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {MetadataType[]}
|
||||
* @readonly
|
||||
*/
|
||||
types: {
|
||||
get: function () {
|
||||
return this._types;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata component types.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {MetadataComponentType[]}
|
||||
* @readonly
|
||||
*/
|
||||
componentTypes: {
|
||||
get: function () {
|
||||
return this._componentTypes;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the ordering of the metadata in the buffers.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {VoxelMetadataOrder}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
metadataOrder: {
|
||||
get: function () {
|
||||
return this._metadataOrder;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata minimum values.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {number[][]|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
minimumValues: {
|
||||
get: function () {
|
||||
return this._minimumValues;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the metadata maximum values.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {number[][]|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumValues: {
|
||||
get: function () {
|
||||
return this._maximumValues;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum number of tiles that exist for this provider.
|
||||
* This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory.
|
||||
* If this value is not known it can be undefined.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
maximumTileCount: {
|
||||
get: function () {
|
||||
return this._maximumTileCount;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The number of levels of detail containing available tiles in the tileset.
|
||||
*
|
||||
* @memberof Cesium3DTilesVoxelProvider.prototype
|
||||
* @type {number|undefined}
|
||||
* @readonly
|
||||
*/
|
||||
availableLevels: {
|
||||
get: function () {
|
||||
return this._availableLevels;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a {@link Cesium3DTilesVoxelProvider} that fetches voxel data from a 3D Tiles tileset.
|
||||
*
|
||||
* @param {Resource|string} url The URL to a tileset JSON file
|
||||
* @returns {Promise<Cesium3DTilesVoxelProvider>} The created provider
|
||||
*
|
||||
* @exception {RuntimeException} Root must have content
|
||||
* @exception {RuntimeException} Root tile content must have 3DTILES_content_voxels extension
|
||||
* @exception {RuntimeException} Root tile must have implicit tiling
|
||||
* @exception {RuntimeException} Tileset must have a metadata schema
|
||||
* @exception {RuntimeException} Only box, region and 3DTILES_bounding_volume_cylinder are supported in Cesium3DTilesVoxelProvider
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const voxelProvider = await Cesium3DTilesVoxelProvider.fromUrl(
|
||||
* "http://localhost:8002/tilesets/voxel/tileset.json"
|
||||
* );
|
||||
* const voxelPrimitive = new VoxelPrimitive({
|
||||
* provider: voxelProvider,
|
||||
* customShader: customShader,
|
||||
* });
|
||||
* scene.primitives.add(voxelPrimitive);
|
||||
* } catch (error) {
|
||||
* console.error(`Error creating voxel primitive: ${error}`);
|
||||
* }
|
||||
*
|
||||
* @see {@link VoxelPrimitive}
|
||||
*/
|
||||
Cesium3DTilesVoxelProvider.fromUrl = async function (url) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("url", url);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const resource = Resource.createIfNeeded(url);
|
||||
const tilesetJson = await resource.fetchJson();
|
||||
|
||||
validate(tilesetJson);
|
||||
|
||||
const schemaLoader = getMetadataSchemaLoader(tilesetJson, resource);
|
||||
await schemaLoader.load();
|
||||
|
||||
const { root } = tilesetJson;
|
||||
|
||||
const metadataJson = hasExtension(tilesetJson, "3DTILES_metadata")
|
||||
? tilesetJson.extensions["3DTILES_metadata"]
|
||||
: tilesetJson;
|
||||
|
||||
const tilesetMetadata = new Cesium3DTilesetMetadata({
|
||||
metadataJson: metadataJson,
|
||||
schema: schemaLoader.schema,
|
||||
});
|
||||
|
||||
const voxel = root.content.extensions["3DTILES_content_voxels"];
|
||||
const className = voxel.class;
|
||||
|
||||
const providerOptions = getAttributeInfo(tilesetMetadata, className);
|
||||
Object.assign(providerOptions, getShape(root));
|
||||
if (defined(root.transform)) {
|
||||
providerOptions.globalTransform = Matrix4.unpack(root.transform);
|
||||
} else {
|
||||
providerOptions.globalTransform = Matrix4.clone(Matrix4.IDENTITY);
|
||||
}
|
||||
|
||||
providerOptions.dimensions = Cartesian3.unpack(voxel.dimensions);
|
||||
providerOptions.maximumTileCount = getTileCount(tilesetMetadata);
|
||||
|
||||
if (defined(voxel.padding)) {
|
||||
providerOptions.paddingBefore = Cartesian3.unpack(voxel.padding.before);
|
||||
providerOptions.paddingAfter = Cartesian3.unpack(voxel.padding.after);
|
||||
}
|
||||
|
||||
const provider = new Cesium3DTilesVoxelProvider(providerOptions);
|
||||
|
||||
const implicitTileset = new ImplicitTileset(
|
||||
resource,
|
||||
root,
|
||||
schemaLoader.schema,
|
||||
);
|
||||
provider._implicitTileset = implicitTileset;
|
||||
provider._availableLevels = implicitTileset.availableLevels;
|
||||
|
||||
ResourceCache.unload(schemaLoader);
|
||||
|
||||
return provider;
|
||||
};
|
||||
|
||||
function getTileCount(metadata) {
|
||||
if (!defined(metadata.tileset)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return metadata.tileset.getPropertyBySemantic(
|
||||
MetadataSemantic.TILESET_TILE_COUNT,
|
||||
);
|
||||
}
|
||||
|
||||
function validate(tileset) {
|
||||
const root = tileset.root;
|
||||
|
||||
if (!defined(root.content)) {
|
||||
throw new RuntimeError("Root must have content");
|
||||
}
|
||||
|
||||
if (!hasExtension(root.content, "3DTILES_content_voxels")) {
|
||||
throw new RuntimeError(
|
||||
"Root tile content must have 3DTILES_content_voxels extension",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!hasExtension(root, "3DTILES_implicit_tiling") &&
|
||||
!defined(root.implicitTiling)
|
||||
) {
|
||||
throw new RuntimeError("Root tile must have implicit tiling");
|
||||
}
|
||||
|
||||
if (
|
||||
!defined(tileset.schema) &&
|
||||
!defined(tileset.schemaUri) &&
|
||||
!hasExtension(tileset, "3DTILES_metadata")
|
||||
) {
|
||||
throw new RuntimeError("Tileset must have a metadata schema");
|
||||
}
|
||||
}
|
||||
|
||||
function getShape(tile) {
|
||||
const boundingVolume = tile.boundingVolume;
|
||||
|
||||
if (defined(boundingVolume.box)) {
|
||||
return getBoxShape(boundingVolume.box);
|
||||
} else if (defined(boundingVolume.region)) {
|
||||
return getEllipsoidShape(boundingVolume.region);
|
||||
} else if (hasExtension(boundingVolume, "3DTILES_bounding_volume_cylinder")) {
|
||||
return getCylinderShape(
|
||||
boundingVolume.extensions["3DTILES_bounding_volume_cylinder"],
|
||||
);
|
||||
}
|
||||
|
||||
throw new RuntimeError(
|
||||
"Only box, region and 3DTILES_bounding_volume_cylinder are supported in Cesium3DTilesVoxelProvider",
|
||||
);
|
||||
}
|
||||
|
||||
function getEllipsoidShape(region) {
|
||||
const west = region[0];
|
||||
const south = region[1];
|
||||
const east = region[2];
|
||||
const north = region[3];
|
||||
const minHeight = region[4];
|
||||
const maxHeight = region[5];
|
||||
|
||||
const shapeTransform = Matrix4.fromScale(Ellipsoid.WGS84.radii);
|
||||
|
||||
const minBounds = new Cartesian3(west, south, minHeight);
|
||||
const maxBounds = new Cartesian3(east, north, maxHeight);
|
||||
|
||||
return {
|
||||
shape: VoxelShapeType.ELLIPSOID,
|
||||
minBounds: minBounds,
|
||||
maxBounds: maxBounds,
|
||||
shapeTransform: shapeTransform,
|
||||
};
|
||||
}
|
||||
|
||||
const scratchScale = new Cartesian3();
|
||||
const scratchRotation = new Matrix3();
|
||||
|
||||
function getBoxShape(box) {
|
||||
const obb = OrientedBoundingBox.unpack(box);
|
||||
const scale = Matrix3.getScale(obb.halfAxes, scratchScale);
|
||||
const rotation = Matrix3.getRotation(obb.halfAxes, scratchRotation);
|
||||
|
||||
return {
|
||||
shape: VoxelShapeType.BOX,
|
||||
minBounds: Cartesian3.negate(scale, new Cartesian3()),
|
||||
maxBounds: Cartesian3.clone(scale),
|
||||
shapeTransform: Matrix4.fromRotationTranslation(rotation, obb.center),
|
||||
};
|
||||
}
|
||||
|
||||
function getCylinderShape(cylinder) {
|
||||
const {
|
||||
minRadius,
|
||||
maxRadius,
|
||||
height,
|
||||
minAngle = -CesiumMath.PI,
|
||||
maxAngle = CesiumMath.PI,
|
||||
translation = [0, 0, 0],
|
||||
rotation = [0, 0, 0, 1],
|
||||
} = cylinder;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("minRadius", minRadius);
|
||||
Check.typeOf.number("maxRadius", maxRadius);
|
||||
Check.typeOf.number("height", height);
|
||||
Check.typeOf.number("minAngle", minAngle);
|
||||
Check.typeOf.number("maxAngle", maxAngle);
|
||||
Check.typeOf.object("translation", translation);
|
||||
Check.typeOf.object("rotation", rotation);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const minHeight = -0.5 * height + translation[2];
|
||||
const maxHeight = 0.5 * height + translation[2];
|
||||
|
||||
const shapeTransform = Matrix4.fromTranslationQuaternionRotationScale(
|
||||
Cartesian3.unpack(translation),
|
||||
Quaternion.unpack(rotation),
|
||||
Cartesian3.ONE,
|
||||
);
|
||||
|
||||
return {
|
||||
shape: VoxelShapeType.CYLINDER,
|
||||
minBounds: Cartesian3.fromElements(minRadius, minAngle, minHeight),
|
||||
maxBounds: Cartesian3.fromElements(maxRadius, maxAngle, maxHeight),
|
||||
shapeTransform: shapeTransform,
|
||||
};
|
||||
}
|
||||
|
||||
function getMetadataSchemaLoader(tilesetJson, resource) {
|
||||
const { schemaUri, schema } = tilesetJson;
|
||||
if (!defined(schemaUri)) {
|
||||
return ResourceCache.getSchemaLoader({ schema });
|
||||
}
|
||||
return ResourceCache.getSchemaLoader({
|
||||
resource: resource.getDerivedResource({
|
||||
url: schemaUri,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function getAttributeInfo(metadata, className) {
|
||||
const { schema, statistics } = metadata;
|
||||
const classStatistics = statistics?.classes[className];
|
||||
const properties = schema.classes[className].properties;
|
||||
|
||||
const propertyInfo = Object.entries(properties).map(([id, property]) => {
|
||||
const { type, componentType } = property;
|
||||
const min = classStatistics?.properties[id].min;
|
||||
const max = classStatistics?.properties[id].max;
|
||||
const componentCount = MetadataType.getComponentCount(type);
|
||||
const minValue = copyArray(min, componentCount);
|
||||
const maxValue = copyArray(max, componentCount);
|
||||
|
||||
return { id, type, componentType, minValue, maxValue };
|
||||
});
|
||||
|
||||
const names = propertyInfo.map((info) => info.id);
|
||||
const types = propertyInfo.map((info) => info.type);
|
||||
const componentTypes = propertyInfo.map((info) => info.componentType);
|
||||
|
||||
const minimumValues = propertyInfo.map((info) => info.minValue);
|
||||
const maximumValues = propertyInfo.map((info) => info.maxValue);
|
||||
const hasMinimumValues = minimumValues.some(defined);
|
||||
|
||||
return {
|
||||
className,
|
||||
names,
|
||||
types,
|
||||
componentTypes,
|
||||
minimumValues: hasMinimumValues ? minimumValues : undefined,
|
||||
maximumValues: hasMinimumValues ? maximumValues : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function copyArray(values, length) {
|
||||
// Copy input values into a new array of a specified length.
|
||||
// If the input is not an array, its value will be copied into the first element
|
||||
// of the returned array. If the input is an array shorter than the returned
|
||||
// array, the extra elements in the returned array will be undefined. If the
|
||||
// input is undefined, the return will be undefined.
|
||||
if (!defined(values)) {
|
||||
return;
|
||||
}
|
||||
const valuesArray = Array.isArray(values) ? values : [values];
|
||||
return Array.from({ length }, (v, i) => valuesArray[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subtree at a given subtree coordinate
|
||||
* @param {VoxelProvider} provider The voxel provider
|
||||
* @param {ImplicitTileCoordinates} subtreeCoord The coordinate at which to retrieve the subtree
|
||||
* @returns {Promise<ImplicitSubtree>} The subtree at the given coordinate
|
||||
* @private
|
||||
*/
|
||||
async function getSubtree(provider, subtreeCoord) {
|
||||
const implicitTileset = provider._implicitTileset;
|
||||
const subtreeCache = provider._subtreeCache;
|
||||
|
||||
// First load the subtree to check if the tile is available.
|
||||
// If the subtree has been requested previously it might still be in the cache
|
||||
let subtree = subtreeCache.find(subtreeCoord);
|
||||
if (defined(subtree)) {
|
||||
return subtree;
|
||||
}
|
||||
|
||||
const subtreeRelative = implicitTileset.subtreeUriTemplate.getDerivedResource(
|
||||
{
|
||||
templateValues: subtreeCoord.getTemplateValues(),
|
||||
},
|
||||
);
|
||||
const subtreeResource = implicitTileset.baseResource.getDerivedResource({
|
||||
url: subtreeRelative.url,
|
||||
});
|
||||
|
||||
const arrayBuffer = await subtreeResource.fetchArrayBuffer();
|
||||
// Check one more time if the subtree is in the cache.
|
||||
// This could happen if there are two in-flight tile requests from the same
|
||||
// subtree and one finishes before the other.
|
||||
subtree = subtreeCache.find(subtreeCoord);
|
||||
if (defined(subtree)) {
|
||||
return subtree;
|
||||
}
|
||||
|
||||
const preprocessed = preprocess3DTileContent(arrayBuffer);
|
||||
subtree = await ImplicitSubtree.fromSubtreeJson(
|
||||
subtreeResource,
|
||||
preprocessed.jsonPayload,
|
||||
preprocessed.binaryPayload,
|
||||
implicitTileset,
|
||||
subtreeCoord,
|
||||
);
|
||||
subtreeCache.addSubtree(subtree);
|
||||
return subtree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the data for a given tile.
|
||||
*
|
||||
* @param {object} [options] Object with the following properties:
|
||||
* @param {number} [options.tileLevel=0] The tile's level.
|
||||
* @param {number} [options.tileX=0] The tile's X coordinate.
|
||||
* @param {number} [options.tileY=0] The tile's Y coordinate.
|
||||
* @param {number} [options.tileZ=0] The tile's Z coordinate.
|
||||
* @privateparam {number} [options.keyframe=0] The requested keyframe.
|
||||
* @returns {Promise<VoxelContent>|undefined} A promise resolving to a VoxelContent containing the data for the tile, or undefined if the request could not be scheduled this frame.
|
||||
*/
|
||||
Cesium3DTilesVoxelProvider.prototype.requestData = async function (options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
const {
|
||||
tileLevel = 0,
|
||||
tileX = 0,
|
||||
tileY = 0,
|
||||
tileZ = 0,
|
||||
keyframe = 0,
|
||||
} = options;
|
||||
|
||||
if (keyframe !== 0) {
|
||||
return Promise.reject(
|
||||
`3D Tiles currently doesn't support time-dynamic data.`,
|
||||
);
|
||||
}
|
||||
|
||||
// 1. Load the subtree that the tile belongs to (possibly from the subtree cache)
|
||||
// 2. Load the voxel content if available
|
||||
|
||||
// Can't use a scratch variable here because the object is used inside the promise chain.
|
||||
const implicitTileset = this._implicitTileset;
|
||||
const tileCoordinates = new ImplicitTileCoordinates({
|
||||
subdivisionScheme: implicitTileset.subdivisionScheme,
|
||||
subtreeLevels: implicitTileset.subtreeLevels,
|
||||
level: tileLevel,
|
||||
x: tileX,
|
||||
y: tileY,
|
||||
z: tileZ,
|
||||
});
|
||||
|
||||
// Find the coordinates of the parent subtree containing tileCoordinates
|
||||
// If tileCoordinates is a subtree child, use that subtree
|
||||
// If tileCoordinates is a subtree root, use its parent subtree
|
||||
const isSubtreeRoot =
|
||||
tileCoordinates.isSubtreeRoot() && tileCoordinates.level > 0;
|
||||
|
||||
const subtreeCoord = isSubtreeRoot
|
||||
? tileCoordinates.getParentSubtreeCoordinates()
|
||||
: tileCoordinates.getSubtreeCoordinates();
|
||||
|
||||
const that = this;
|
||||
|
||||
const subtree = await getSubtree(that, subtreeCoord);
|
||||
// NOTE: these two subtree methods are ONLY used by voxels!
|
||||
const isAvailable = isSubtreeRoot
|
||||
? subtree.childSubtreeIsAvailableAtCoordinates
|
||||
: subtree.tileIsAvailableAtCoordinates;
|
||||
|
||||
const available = isAvailable.call(subtree, tileCoordinates);
|
||||
|
||||
if (!available) {
|
||||
return Promise.reject(
|
||||
`Tile is not available at level ${tileLevel}, x ${tileX}, y ${tileY}, z ${tileZ}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { contentUriTemplates, baseResource } = implicitTileset;
|
||||
const gltfRelative = contentUriTemplates[0].getDerivedResource({
|
||||
templateValues: tileCoordinates.getTemplateValues(),
|
||||
});
|
||||
const gltfResource = baseResource.getDerivedResource({
|
||||
url: gltfRelative.url,
|
||||
});
|
||||
|
||||
return VoxelContent.fromGltf(gltfResource);
|
||||
};
|
||||
|
||||
export default Cesium3DTilesVoxelProvider;
|
||||
+3973
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+291
@@ -0,0 +1,291 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import ManagedArray from "../Core/ManagedArray.js";
|
||||
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
|
||||
import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
|
||||
|
||||
/**
|
||||
* Depth-first traversal that traverses all visible tiles and marks tiles for selection.
|
||||
* A tile does not refine until all children are loaded.
|
||||
* This is the traditional replacement refinement approach and is called the base traversal.
|
||||
*
|
||||
* @alias Cesium3DTilesetBaseTraversal
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetBaseTraversal() {}
|
||||
|
||||
const traversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
};
|
||||
|
||||
const emptyTraversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTileset} tileset
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetBaseTraversal.selectTiles = function (tileset, frameState) {
|
||||
tileset._requestedTiles.length = 0;
|
||||
|
||||
if (tileset.debugFreezeFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileset._selectedTiles.length = 0;
|
||||
tileset._selectedTilesToStyle.length = 0;
|
||||
tileset._emptyTiles.length = 0;
|
||||
tileset.hasMixedContent = false;
|
||||
|
||||
const root = tileset.root;
|
||||
Cesium3DTilesetTraversal.updateTile(root, frameState);
|
||||
|
||||
if (!root.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
root.getScreenSpaceError(frameState, true) <=
|
||||
tileset.memoryAdjustedScreenSpaceError
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeTraversal(root, frameState);
|
||||
|
||||
traversal.stack.trim(traversal.stackMaximumLength);
|
||||
emptyTraversal.stack.trim(emptyTraversal.stackMaximumLength);
|
||||
|
||||
// Update the priority for any requests found during traversal
|
||||
// Update after traversal so that min and max values can be used to normalize priority values
|
||||
const requestedTiles = tileset._requestedTiles;
|
||||
for (let i = 0; i < requestedTiles.length; ++i) {
|
||||
requestedTiles[i].updatePriority();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark a tile as selected if it has content available.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function selectDesiredTile(tile, frameState) {
|
||||
if (tile.contentAvailable) {
|
||||
Cesium3DTilesetTraversal.selectTile(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {ManagedArray} stack
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function updateAndPushChildren(tile, stack, frameState) {
|
||||
const replace = tile.refine === Cesium3DTileRefine.REPLACE;
|
||||
const { tileset, children } = tile;
|
||||
const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
updateTile(children[i], frameState);
|
||||
}
|
||||
|
||||
// Sort by distance to take advantage of early Z and reduce artifacts for skipLevelOfDetail
|
||||
children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
|
||||
|
||||
// For traditional replacement refinement only refine if all children are loaded.
|
||||
// Empty tiles are exempt since it looks better if children stream in as they are loaded to fill the empty space.
|
||||
const checkRefines = replace && tile.hasRenderableContent;
|
||||
let refines = true;
|
||||
|
||||
let anyChildrenVisible = false;
|
||||
|
||||
// Determining min child
|
||||
let minIndex = -1;
|
||||
let minimumPriority = Number.MAX_VALUE;
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
if (child.isVisible) {
|
||||
stack.push(child);
|
||||
if (child._foveatedFactor < minimumPriority) {
|
||||
minIndex = i;
|
||||
minimumPriority = child._foveatedFactor;
|
||||
}
|
||||
anyChildrenVisible = true;
|
||||
} else if (checkRefines || tileset.loadSiblings) {
|
||||
// Keep non-visible children loaded since they are still needed before the parent can refine.
|
||||
// Or loadSiblings is true so always load tiles regardless of visibility.
|
||||
if (child._foveatedFactor < minimumPriority) {
|
||||
minIndex = i;
|
||||
minimumPriority = child._foveatedFactor;
|
||||
}
|
||||
loadTile(child, frameState);
|
||||
touchTile(child, frameState);
|
||||
}
|
||||
if (checkRefines) {
|
||||
let childRefines;
|
||||
if (!child._inRequestVolume) {
|
||||
childRefines = false;
|
||||
} else if (!child.hasRenderableContent) {
|
||||
childRefines = executeEmptyTraversal(child, frameState);
|
||||
} else {
|
||||
childRefines = child.contentAvailable;
|
||||
}
|
||||
refines = refines && childRefines;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyChildrenVisible) {
|
||||
refines = false;
|
||||
}
|
||||
|
||||
if (minIndex !== -1 && replace) {
|
||||
// An ancestor will hold the _foveatedFactor and _distanceToCamera for descendants between itself and its highest priority descendant. Siblings of a min children along the way use this ancestor as their priority holder as well.
|
||||
// Priority of all tiles that refer to the _foveatedFactor and _distanceToCamera stored in the common ancestor will be differentiated based on their _depth.
|
||||
const minPriorityChild = children[minIndex];
|
||||
minPriorityChild._wasMinPriorityChild = true;
|
||||
const priorityHolder =
|
||||
(tile._wasMinPriorityChild || tile === tileset.root) &&
|
||||
minimumPriority <= tile._priorityHolder._foveatedFactor
|
||||
? tile._priorityHolder
|
||||
: tile; // This is where priority dependency chains are wired up or started anew.
|
||||
priorityHolder._foveatedFactor = Math.min(
|
||||
minPriorityChild._foveatedFactor,
|
||||
priorityHolder._foveatedFactor,
|
||||
);
|
||||
priorityHolder._distanceToCamera = Math.min(
|
||||
minPriorityChild._distanceToCamera,
|
||||
priorityHolder._distanceToCamera,
|
||||
);
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
children[i]._priorityHolder = priorityHolder;
|
||||
}
|
||||
}
|
||||
|
||||
return refines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Depth-first traversal that traverses all visible tiles and marks tiles for selection.
|
||||
* A tile does not refine until all children are loaded.
|
||||
* This is the traditional replacement refinement approach and is called the base traversal.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} root
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function executeTraversal(root, frameState) {
|
||||
const { tileset } = root;
|
||||
|
||||
const { canTraverse, loadTile, visitTile, touchTile } =
|
||||
Cesium3DTilesetTraversal;
|
||||
const stack = traversal.stack;
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
traversal.stackMaximumLength = Math.max(
|
||||
traversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
|
||||
const tile = stack.pop();
|
||||
|
||||
const parent = tile.parent;
|
||||
const parentRefines = !defined(parent) || parent._refines;
|
||||
|
||||
tile._refines = canTraverse(tile)
|
||||
? updateAndPushChildren(tile, stack, frameState) && parentRefines
|
||||
: false;
|
||||
|
||||
const stoppedRefining = !tile._refines && parentRefines;
|
||||
|
||||
if (!tile.hasRenderableContent) {
|
||||
// Add empty tile just to show its debug bounding volume
|
||||
// If the tile has tileset content load the external tileset
|
||||
tileset._emptyTiles.push(tile);
|
||||
loadTile(tile, frameState);
|
||||
if (stoppedRefining) {
|
||||
selectDesiredTile(tile, frameState);
|
||||
}
|
||||
} else if (tile.refine === Cesium3DTileRefine.ADD) {
|
||||
// Additive tiles are always loaded and selected
|
||||
selectDesiredTile(tile, frameState);
|
||||
loadTile(tile, frameState);
|
||||
} else if (tile.refine === Cesium3DTileRefine.REPLACE) {
|
||||
loadTile(tile, frameState);
|
||||
if (stoppedRefining) {
|
||||
selectDesiredTile(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
visitTile(tile, frameState);
|
||||
touchTile(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Depth-first traversal that checks if all nearest descendants with content are loaded.
|
||||
* Ignores visibility.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} root
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function executeEmptyTraversal(root, frameState) {
|
||||
const { canTraverse, updateTile, loadTile, touchTile } =
|
||||
Cesium3DTilesetTraversal;
|
||||
let allDescendantsLoaded = true;
|
||||
const stack = emptyTraversal.stack;
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
emptyTraversal.stackMaximumLength = Math.max(
|
||||
emptyTraversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
|
||||
const tile = stack.pop();
|
||||
const children = tile.children;
|
||||
const childrenLength = children.length;
|
||||
|
||||
// Only traverse if the tile is empty - traversal stops at descendants with content
|
||||
const traverse = !tile.hasRenderableContent && canTraverse(tile);
|
||||
|
||||
// Traversal stops but the tile does not have content yet
|
||||
// There will be holes if the parent tries to refine to its children, so don't refine
|
||||
if (!traverse && !tile.contentAvailable) {
|
||||
allDescendantsLoaded = false;
|
||||
}
|
||||
|
||||
updateTile(tile, frameState);
|
||||
if (!tile.isVisible) {
|
||||
// Load tiles that aren't visible since they are still needed for the parent to refine
|
||||
loadTile(tile, frameState);
|
||||
touchTile(tile, frameState);
|
||||
}
|
||||
|
||||
if (traverse) {
|
||||
for (let i = 0; i < childrenLength; ++i) {
|
||||
const child = children[i];
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root.hasEmptyContent || allDescendantsLoaded;
|
||||
}
|
||||
|
||||
export default Cesium3DTilesetBaseTraversal;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import DoublyLinkedList from "../Core/DoublyLinkedList.js";
|
||||
|
||||
/**
|
||||
* Stores tiles with content loaded.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetCache() {
|
||||
// [head, sentinel) -> tiles that weren't selected this frame and may be removed from the cache
|
||||
// (sentinel, tail] -> tiles that were selected this frame
|
||||
this._list = new DoublyLinkedList();
|
||||
this._sentinel = this._list.add();
|
||||
this._trimTiles = false;
|
||||
}
|
||||
|
||||
Cesium3DTilesetCache.prototype.reset = function () {
|
||||
// Move sentinel node to the tail so, at the start of the frame, all tiles
|
||||
// may be potentially replaced. Tiles are moved to the right of the sentinel
|
||||
// when they are selected so they will not be replaced.
|
||||
this._list.splice(this._list.tail, this._sentinel);
|
||||
};
|
||||
|
||||
Cesium3DTilesetCache.prototype.touch = function (tile) {
|
||||
const node = tile.cacheNode;
|
||||
if (defined(node)) {
|
||||
this._list.splice(this._sentinel, node);
|
||||
}
|
||||
};
|
||||
|
||||
Cesium3DTilesetCache.prototype.add = function (tile) {
|
||||
if (!defined(tile.cacheNode)) {
|
||||
tile.cacheNode = this._list.add(tile);
|
||||
}
|
||||
};
|
||||
|
||||
Cesium3DTilesetCache.prototype.unloadTile = function (
|
||||
tileset,
|
||||
tile,
|
||||
unloadCallback,
|
||||
) {
|
||||
const node = tile.cacheNode;
|
||||
if (!defined(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._list.remove(node);
|
||||
tile.cacheNode = undefined;
|
||||
unloadCallback(tileset, tile);
|
||||
};
|
||||
|
||||
Cesium3DTilesetCache.prototype.unloadTiles = function (
|
||||
tileset,
|
||||
unloadCallback,
|
||||
) {
|
||||
const trimTiles = this._trimTiles;
|
||||
this._trimTiles = false;
|
||||
|
||||
const list = this._list;
|
||||
|
||||
// Traverse the list only to the sentinel since tiles/nodes to the
|
||||
// right of the sentinel were used this frame.
|
||||
//
|
||||
// The sub-list to the left of the sentinel is ordered from LRU to MRU.
|
||||
const sentinel = this._sentinel;
|
||||
let node = list.head;
|
||||
while (
|
||||
node !== sentinel &&
|
||||
(tileset.totalMemoryUsageInBytes > tileset.cacheBytes || trimTiles)
|
||||
) {
|
||||
const tile = node.item;
|
||||
node = node.next;
|
||||
this.unloadTile(tileset, tile, unloadCallback);
|
||||
}
|
||||
};
|
||||
|
||||
Cesium3DTilesetCache.prototype.trim = function () {
|
||||
this._trimTiles = true;
|
||||
};
|
||||
export default Cesium3DTilesetCache;
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import Color from "../Core/Color.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import JulianDate from "../Core/JulianDate.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
/**
|
||||
* A heatmap colorizer in a {@link Cesium3DTileset}. A tileset can colorize its visible tiles in a heatmap style.
|
||||
*
|
||||
* @alias Cesium3DTilesetHeatmap
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetHeatmap(tilePropertyName) {
|
||||
/**
|
||||
* The tile variable to track for heatmap colorization.
|
||||
* Tile's will be colorized relative to the other visible tile's values for this variable.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.tilePropertyName = tilePropertyName;
|
||||
|
||||
// Members that are updated every time a tile is colorized
|
||||
this._minimum = Number.MAX_VALUE;
|
||||
this._maximum = -Number.MAX_VALUE;
|
||||
|
||||
// Members that are updated once every frame
|
||||
this._previousMinimum = Number.MAX_VALUE;
|
||||
this._previousMaximum = -Number.MAX_VALUE;
|
||||
|
||||
// If defined uses a reference minimum maximum to colorize by instead of using last frames minimum maximum of rendered tiles.
|
||||
// For example, the _loadTimestamp can get a better colorization using setReferenceMinimumMaximum in order to take accurate colored timing diffs of various scenes.
|
||||
this._referenceMinimum = {};
|
||||
this._referenceMaximum = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a usable heatmap value (i.e. a number). Ensures that tile values that aren't stored as numbers can be used for colorization.
|
||||
* @private
|
||||
*/
|
||||
function getHeatmapValue(tileValue, tilePropertyName) {
|
||||
let value;
|
||||
if (tilePropertyName === "_loadTimestamp") {
|
||||
value = JulianDate.toDate(tileValue).getTime();
|
||||
} else {
|
||||
value = tileValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference minimum and maximum for the variable name. Converted to numbers before they are stored.
|
||||
*
|
||||
* @param {object} minimum The minimum reference value.
|
||||
* @param {object} maximum The maximum reference value.
|
||||
* @param {string} tilePropertyName The tile variable that will use these reference values when it is colorized.
|
||||
*/
|
||||
Cesium3DTilesetHeatmap.prototype.setReferenceMinimumMaximum = function (
|
||||
minimum,
|
||||
maximum,
|
||||
tilePropertyName,
|
||||
) {
|
||||
this._referenceMinimum[tilePropertyName] = getHeatmapValue(
|
||||
minimum,
|
||||
tilePropertyName,
|
||||
);
|
||||
this._referenceMaximum[tilePropertyName] = getHeatmapValue(
|
||||
maximum,
|
||||
tilePropertyName,
|
||||
);
|
||||
};
|
||||
|
||||
function getHeatmapValueAndUpdateMinimumMaximum(heatmap, tile) {
|
||||
const tilePropertyName = heatmap.tilePropertyName;
|
||||
if (defined(tilePropertyName)) {
|
||||
const heatmapValue = getHeatmapValue(
|
||||
tile[tilePropertyName],
|
||||
tilePropertyName,
|
||||
);
|
||||
if (!defined(heatmapValue)) {
|
||||
heatmap.tilePropertyName = undefined;
|
||||
return heatmapValue;
|
||||
}
|
||||
heatmap._maximum = Math.max(heatmapValue, heatmap._maximum);
|
||||
heatmap._minimum = Math.min(heatmapValue, heatmap._minimum);
|
||||
return heatmapValue;
|
||||
}
|
||||
}
|
||||
|
||||
const heatmapColors = [
|
||||
new Color(0.1, 0.1, 0.1, 1), // Dark Gray
|
||||
new Color(0.153, 0.278, 0.878, 1), // Blue
|
||||
new Color(0.827, 0.231, 0.49, 1), // Pink
|
||||
new Color(0.827, 0.188, 0.22, 1), // Red
|
||||
new Color(1.0, 0.592, 0.259, 1), // Orange
|
||||
new Color(1.0, 0.843, 0.0, 1),
|
||||
]; // Yellow
|
||||
/**
|
||||
* Colorize the tile in heat map style based on where it lies within the minimum maximum window.
|
||||
* Heatmap colors are black, blue, pink, red, orange, yellow. 'Cold' or low numbers will be black and blue, 'Hot' or high numbers will be orange and yellow,
|
||||
* @param {Cesium3DTile} tile The tile to colorize relative to last frame's minimum and maximum values of all visible tiles.
|
||||
* @param {FrameState} frameState The frame state.
|
||||
*/
|
||||
Cesium3DTilesetHeatmap.prototype.colorize = function (tile, frameState) {
|
||||
const tilePropertyName = this.tilePropertyName;
|
||||
if (
|
||||
!defined(tilePropertyName) ||
|
||||
!tile.contentAvailable ||
|
||||
tile._selectedFrame !== frameState.frameNumber
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const heatmapValue = getHeatmapValueAndUpdateMinimumMaximum(this, tile);
|
||||
const minimum = this._previousMinimum;
|
||||
const maximum = this._previousMaximum;
|
||||
|
||||
if (minimum === Number.MAX_VALUE || maximum === -Number.MAX_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift the minimum maximum window down to 0
|
||||
const shiftedMax = maximum - minimum + CesiumMath.EPSILON7; // Prevent divide by 0
|
||||
const shiftedValue = CesiumMath.clamp(
|
||||
heatmapValue - minimum,
|
||||
0.0,
|
||||
shiftedMax,
|
||||
);
|
||||
|
||||
// Get position between minimum and maximum and convert that to a position in the color array
|
||||
const zeroToOne = shiftedValue / shiftedMax;
|
||||
const lastIndex = heatmapColors.length - 1.0;
|
||||
const colorPosition = zeroToOne * lastIndex;
|
||||
|
||||
// Take floor and ceil of the value to get the two colors to lerp between, lerp using the fractional portion
|
||||
const colorPositionFloor = Math.floor(colorPosition);
|
||||
const colorPositionCeil = Math.ceil(colorPosition);
|
||||
const t = colorPosition - colorPositionFloor;
|
||||
const colorZero = heatmapColors[colorPositionFloor];
|
||||
const colorOne = heatmapColors[colorPositionCeil];
|
||||
|
||||
// Perform the lerp
|
||||
const finalColor = Color.clone(Color.WHITE);
|
||||
finalColor.red = CesiumMath.lerp(colorZero.red, colorOne.red, t);
|
||||
finalColor.green = CesiumMath.lerp(colorZero.green, colorOne.green, t);
|
||||
finalColor.blue = CesiumMath.lerp(colorZero.blue, colorOne.blue, t);
|
||||
tile._debugColor = finalColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the tracked minimum maximum values for heatmap colorization. Happens right before tileset traversal.
|
||||
*/
|
||||
Cesium3DTilesetHeatmap.prototype.resetMinimumMaximum = function () {
|
||||
// For heat map colorization
|
||||
const tilePropertyName = this.tilePropertyName;
|
||||
if (defined(tilePropertyName)) {
|
||||
const referenceMinimum = this._referenceMinimum[tilePropertyName];
|
||||
const referenceMaximum = this._referenceMaximum[tilePropertyName];
|
||||
const useReference = defined(referenceMinimum) && defined(referenceMaximum);
|
||||
this._previousMinimum = useReference ? referenceMinimum : this._minimum;
|
||||
this._previousMaximum = useReference ? referenceMaximum : this._maximum;
|
||||
this._minimum = Number.MAX_VALUE;
|
||||
this._maximum = -Number.MAX_VALUE;
|
||||
}
|
||||
};
|
||||
export default Cesium3DTilesetHeatmap;
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import GroupMetadata from "./GroupMetadata.js";
|
||||
import TilesetMetadata from "./TilesetMetadata.js";
|
||||
|
||||
/**
|
||||
* An object containing metadata about a 3D Tileset.
|
||||
* <p>
|
||||
* See the {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata|3DTILES_metadata Extension} for 3D Tiles.
|
||||
* </p>
|
||||
* <p>
|
||||
* This object represents the tileset JSON (3D Tiles 1.1) or the <code>3DTILES_metadata</code> object that contains
|
||||
* the schema ({@link MetadataSchema}), tileset metadata ({@link TilesetMetadata}), group metadata (dictionary of {@link GroupMetadata}), and metadata statistics (dictionary)
|
||||
* </p>
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {object} options.metadataJson Either the tileset JSON (3D Tiles 1.1) or the <code>3DTILES_metadata</code> extension object that contains the tileset metadata.
|
||||
* @param {MetadataSchema} options.schema The parsed schema.
|
||||
*
|
||||
* @alias Cesium3DTilesetMetadata
|
||||
* @constructor
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
function Cesium3DTilesetMetadata(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
const metadataJson = options.metadataJson;
|
||||
|
||||
// The calling code is responsible for loading the schema.
|
||||
// This keeps metadata parsing synchronous.
|
||||
const schema = options.schema;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options.metadataJson", metadataJson);
|
||||
Check.typeOf.object("options.schema", schema);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
// An older schema stored the tileset metadata in the "tileset" property.
|
||||
const metadata = metadataJson.metadata ?? metadataJson.tileset;
|
||||
|
||||
let tileset;
|
||||
if (defined(metadata)) {
|
||||
tileset = new TilesetMetadata({
|
||||
tileset: metadata,
|
||||
class: schema.classes[metadata.class],
|
||||
});
|
||||
}
|
||||
|
||||
let groupIds = [];
|
||||
const groups = [];
|
||||
const groupsJson = metadataJson.groups;
|
||||
if (Array.isArray(groupsJson)) {
|
||||
const length = groupsJson.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const group = groupsJson[i];
|
||||
groups.push(
|
||||
new GroupMetadata({
|
||||
group: group,
|
||||
class: schema.classes[group.class],
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if (defined(groupsJson)) {
|
||||
// An older version of group metadata stored groups in a dictionary
|
||||
// instead of an array.
|
||||
groupIds = Object.keys(groupsJson).sort();
|
||||
const length = groupIds.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const groupId = groupIds[i];
|
||||
if (groupsJson.hasOwnProperty(groupId)) {
|
||||
const group = groupsJson[groupId];
|
||||
groups.push(
|
||||
new GroupMetadata({
|
||||
id: groupId,
|
||||
group: groupsJson[groupId],
|
||||
class: schema.classes[group.class],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._schema = schema;
|
||||
this._groups = groups;
|
||||
this._groupIds = groupIds;
|
||||
this._tileset = tileset;
|
||||
|
||||
this._statistics = metadataJson.statistics;
|
||||
this._extras = metadataJson.extras;
|
||||
this._extensions = metadataJson.extensions;
|
||||
}
|
||||
|
||||
Object.defineProperties(Cesium3DTilesetMetadata.prototype, {
|
||||
/**
|
||||
* Schema containing classes and enums.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {MetadataSchema}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
schema: {
|
||||
get: function () {
|
||||
return this._schema;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Metadata about groups of content.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {GroupMetadata[]}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
groups: {
|
||||
get: function () {
|
||||
return this._groups;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The IDs of the group metadata in the corresponding groups dictionary.
|
||||
* Only populated if using the legacy schema.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
groupIds: {
|
||||
get: function () {
|
||||
return this._groupIds;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Metadata about the tileset as a whole.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {TilesetMetadata}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
tileset: {
|
||||
get: function () {
|
||||
return this._tileset;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Statistics about the metadata.
|
||||
* <p>
|
||||
* See the {@link https://github.com/CesiumGS/3d-tiles/blob/main/extensions/3DTILES_metadata/schema/statistics.schema.json|statistics schema reference}
|
||||
* in the 3D Tiles spec for the full set of properties.
|
||||
* </p>
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {object}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
statistics: {
|
||||
get: function () {
|
||||
return this._statistics;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Extra user-defined properties.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {*}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
extras: {
|
||||
get: function () {
|
||||
return this._extras;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* An object containing extensions.
|
||||
*
|
||||
* @memberof Cesium3DTilesetMetadata.prototype
|
||||
* @type {object}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
extensions: {
|
||||
get: function () {
|
||||
return this._extensions;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default Cesium3DTilesetMetadata;
|
||||
Generated
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
import Intersect from "../Core/Intersect.js";
|
||||
import ManagedArray from "../Core/ManagedArray.js";
|
||||
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
|
||||
import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
|
||||
|
||||
/**
|
||||
* Traversal that loads all leaves that intersect the camera frustum.
|
||||
* Used to determine ray-tileset intersections during a pickFromRayMostDetailed call.
|
||||
*
|
||||
* @alias Cesium3DTilesetMostDetailedTraversal
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetMostDetailedTraversal() {}
|
||||
|
||||
const traversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTileset} tileset
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean} Whether the appropriate tile is ready for picking
|
||||
*/
|
||||
Cesium3DTilesetMostDetailedTraversal.selectTiles = function (
|
||||
tileset,
|
||||
frameState,
|
||||
) {
|
||||
tileset._selectedTiles.length = 0;
|
||||
tileset._requestedTiles.length = 0;
|
||||
tileset.hasMixedContent = false;
|
||||
|
||||
let ready = true;
|
||||
|
||||
const root = tileset.root;
|
||||
root.updateVisibility(frameState);
|
||||
|
||||
if (!root.isVisible) {
|
||||
return ready;
|
||||
}
|
||||
|
||||
const { touchTile, visitTile } = Cesium3DTilesetTraversal;
|
||||
|
||||
const stack = traversal.stack;
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
traversal.stackMaximumLength = Math.max(
|
||||
traversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
|
||||
const tile = stack.pop();
|
||||
const add = tile.refine === Cesium3DTileRefine.ADD;
|
||||
const replace = tile.refine === Cesium3DTileRefine.REPLACE;
|
||||
const traverse = canTraverse(tile);
|
||||
|
||||
if (traverse) {
|
||||
updateAndPushChildren(tile, stack, frameState);
|
||||
}
|
||||
|
||||
if (add || (replace && !traverse)) {
|
||||
loadTile(tileset, tile);
|
||||
touchTile(tile, frameState);
|
||||
selectDesiredTile(tile, frameState);
|
||||
|
||||
if (tile.hasRenderableContent && !tile.contentAvailable) {
|
||||
ready = false;
|
||||
}
|
||||
}
|
||||
|
||||
visitTile(tile, frameState);
|
||||
}
|
||||
|
||||
traversal.stack.trim(traversal.stackMaximumLength);
|
||||
|
||||
return ready;
|
||||
};
|
||||
|
||||
function canTraverse(tile) {
|
||||
if (tile.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tile.hasTilesetContent || tile.hasImplicitContent) {
|
||||
// Traverse external tileset to visit its root tile
|
||||
// Don't traverse if the subtree is expired because it will be destroyed
|
||||
return !tile.contentExpired;
|
||||
}
|
||||
|
||||
if (tile.hasEmptyContent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true; // Keep traversing until a leaf is hit
|
||||
}
|
||||
|
||||
function updateAndPushChildren(tile, stack, frameState) {
|
||||
const { children } = tile;
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
child.updateVisibility(frameState);
|
||||
if (child.isVisible) {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadTile(tileset, tile) {
|
||||
if (tile.hasUnloadedRenderableContent || tile.contentExpired) {
|
||||
tile._priority = 0.0; // Highest priority
|
||||
tileset._requestedTiles.push(tile);
|
||||
}
|
||||
}
|
||||
|
||||
function selectDesiredTile(tile, frameState) {
|
||||
if (
|
||||
tile.contentAvailable &&
|
||||
tile.contentVisibility(frameState) !== Intersect.OUTSIDE
|
||||
) {
|
||||
tile.tileset._selectedTiles.push(tile);
|
||||
}
|
||||
}
|
||||
|
||||
export default Cesium3DTilesetMostDetailedTraversal;
|
||||
Generated
Vendored
+420
@@ -0,0 +1,420 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import ManagedArray from "../Core/ManagedArray.js";
|
||||
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
|
||||
import Cesium3DTilesetTraversal from "./Cesium3DTilesetTraversal.js";
|
||||
|
||||
/**
|
||||
* Depth-first traversal that traverses all visible tiles and marks tiles for selection.
|
||||
* Allows for skipping levels of the tree and rendering children and parent tiles simultaneously.
|
||||
*
|
||||
* @alias Cesium3DTilesetSkipTraversal
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetSkipTraversal() {}
|
||||
|
||||
const traversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
};
|
||||
|
||||
const descendantTraversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
};
|
||||
|
||||
const selectionTraversal = {
|
||||
stack: new ManagedArray(),
|
||||
stackMaximumLength: 0,
|
||||
ancestorStack: new ManagedArray(),
|
||||
ancestorStackMaximumLength: 0,
|
||||
};
|
||||
|
||||
const descendantSelectionDepth = 2;
|
||||
|
||||
/**
|
||||
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTileset} tileset
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetSkipTraversal.selectTiles = function (tileset, frameState) {
|
||||
tileset._requestedTiles.length = 0;
|
||||
|
||||
if (tileset.debugFreezeFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileset._selectedTiles.length = 0;
|
||||
tileset._selectedTilesToStyle.length = 0;
|
||||
tileset._emptyTiles.length = 0;
|
||||
tileset.hasMixedContent = false;
|
||||
|
||||
const root = tileset.root;
|
||||
Cesium3DTilesetTraversal.updateTile(root, frameState);
|
||||
|
||||
if (!root.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
root.getScreenSpaceError(frameState, true) <=
|
||||
tileset.memoryAdjustedScreenSpaceError
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeTraversal(root, frameState);
|
||||
traverseAndSelect(root, frameState);
|
||||
|
||||
traversal.stack.trim(traversal.stackMaximumLength);
|
||||
descendantTraversal.stack.trim(descendantTraversal.stackMaximumLength);
|
||||
selectionTraversal.stack.trim(selectionTraversal.stackMaximumLength);
|
||||
selectionTraversal.ancestorStack.trim(
|
||||
selectionTraversal.ancestorStackMaximumLength,
|
||||
);
|
||||
|
||||
// Update the priority for any requests found during traversal
|
||||
// Update after traversal so that min and max values can be used to normalize priority values
|
||||
const requestedTiles = tileset._requestedTiles;
|
||||
for (let i = 0; i < requestedTiles.length; ++i) {
|
||||
requestedTiles[i].updatePriority();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark descendant tiles for rendering, and update as needed
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} root
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function selectDescendants(root, frameState) {
|
||||
const { updateTile, touchTile, selectTile } = Cesium3DTilesetTraversal;
|
||||
const stack = descendantTraversal.stack;
|
||||
stack.push(root);
|
||||
while (stack.length > 0) {
|
||||
descendantTraversal.stackMaximumLength = Math.max(
|
||||
descendantTraversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
const tile = stack.pop();
|
||||
const children = tile.children;
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
if (child.isVisible) {
|
||||
if (child.contentAvailable) {
|
||||
updateTile(child, frameState);
|
||||
touchTile(child, frameState);
|
||||
selectTile(child, frameState);
|
||||
} else if (child._depth - root._depth < descendantSelectionDepth) {
|
||||
// Continue traversing, but not too far
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a tile as selected if it has content available.
|
||||
* If its content is not available, and we are skipping levels of detail,
|
||||
* select an ancestor or descendant tile instead
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function selectDesiredTile(tile, frameState) {
|
||||
// If this tile is not loaded attempt to select its ancestor instead
|
||||
const loadedTile = tile.contentAvailable
|
||||
? tile
|
||||
: tile._ancestorWithContentAvailable;
|
||||
if (defined(loadedTile)) {
|
||||
// Tiles will actually be selected in traverseAndSelect
|
||||
loadedTile._shouldSelect = true;
|
||||
} else {
|
||||
// If no ancestors are ready traverse down and select tiles to minimize empty regions.
|
||||
// This happens often for immediatelyLoadDesiredLevelOfDetail where parent tiles are not necessarily loaded before zooming out.
|
||||
selectDescendants(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update links to the ancestor tiles that have content
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function updateTileAncestorContentLinks(tile, frameState) {
|
||||
tile._ancestorWithContent = undefined;
|
||||
tile._ancestorWithContentAvailable = undefined;
|
||||
|
||||
const { parent } = tile;
|
||||
if (!defined(parent)) {
|
||||
return;
|
||||
}
|
||||
const parentHasContent =
|
||||
!parent.hasUnloadedRenderableContent ||
|
||||
parent._requestedFrame === frameState.frameNumber;
|
||||
|
||||
// ancestorWithContent is an ancestor that has content or has the potential to have
|
||||
// content. Used in conjunction with tileset.skipLevels to know when to skip a tile.
|
||||
tile._ancestorWithContent = parentHasContent
|
||||
? parent
|
||||
: parent._ancestorWithContent;
|
||||
|
||||
// ancestorWithContentAvailable is an ancestor that is rendered if a desired tile is not loaded
|
||||
tile._ancestorWithContentAvailable = parent.contentAvailable
|
||||
? parent
|
||||
: parent._ancestorWithContentAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a tile has reached the limit of level of detail skipping.
|
||||
* If so, it should _not_ be skipped: it should be loaded and rendered
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTileset} tileset
|
||||
* @param {Cesium3DTile} tile
|
||||
* @returns {boolean} true if this tile should not be skipped
|
||||
*/
|
||||
function reachedSkippingThreshold(tileset, tile) {
|
||||
const ancestor = tile._ancestorWithContent;
|
||||
return (
|
||||
!tileset.immediatelyLoadDesiredLevelOfDetail &&
|
||||
(tile._priorityProgressiveResolutionScreenSpaceErrorLeaf ||
|
||||
(defined(ancestor) &&
|
||||
tile._screenSpaceError <
|
||||
ancestor._screenSpaceError / tileset.skipScreenSpaceErrorFactor &&
|
||||
tile._depth > ancestor._depth + tileset.skipLevels))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {ManagedArray} stack
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function updateAndPushChildren(tile, stack, frameState) {
|
||||
const { tileset, children } = tile;
|
||||
const { updateTile, loadTile, touchTile } = Cesium3DTilesetTraversal;
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
updateTile(children[i], frameState);
|
||||
}
|
||||
|
||||
// Sort by distance to take advantage of early Z and reduce artifacts
|
||||
children.sort(Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera);
|
||||
|
||||
let anyChildrenVisible = false;
|
||||
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
if (child.isVisible) {
|
||||
stack.push(child);
|
||||
anyChildrenVisible = true;
|
||||
} else if (tileset.loadSiblings) {
|
||||
loadTile(child, frameState);
|
||||
touchTile(child, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
return anyChildrenVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a tile is part of the base traversal.
|
||||
* If not, this tile could be considered for level of detail skipping
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {number} baseScreenSpaceError
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function inBaseTraversal(tile, baseScreenSpaceError) {
|
||||
const { tileset } = tile;
|
||||
if (tileset.immediatelyLoadDesiredLevelOfDetail) {
|
||||
return false;
|
||||
}
|
||||
if (!defined(tile._ancestorWithContent)) {
|
||||
// Include root or near-root tiles in the base traversal so there is something to select up to
|
||||
return true;
|
||||
}
|
||||
if (tile._screenSpaceError === 0.0) {
|
||||
// If a leaf, use parent's SSE
|
||||
return tile.parent._screenSpaceError > baseScreenSpaceError;
|
||||
}
|
||||
return tile._screenSpaceError > baseScreenSpaceError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Depth-first traversal that traverses all visible tiles and marks tiles for selection.
|
||||
* Tiles that have a greater screen space error than the base screen space error are part of the base traversal,
|
||||
* all other tiles are part of the skip traversal. The skip traversal allows for skipping levels of the tree
|
||||
* and rendering children and parent tiles simultaneously.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} root
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function executeTraversal(root, frameState) {
|
||||
const { tileset } = root;
|
||||
const baseScreenSpaceError = tileset.immediatelyLoadDesiredLevelOfDetail
|
||||
? Number.MAX_VALUE
|
||||
: Math.max(
|
||||
tileset.baseScreenSpaceError,
|
||||
tileset.memoryAdjustedScreenSpaceError,
|
||||
);
|
||||
const { canTraverse, loadTile, visitTile, touchTile } =
|
||||
Cesium3DTilesetTraversal;
|
||||
const stack = traversal.stack;
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0) {
|
||||
traversal.stackMaximumLength = Math.max(
|
||||
traversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
|
||||
const tile = stack.pop();
|
||||
|
||||
updateTileAncestorContentLinks(tile, frameState);
|
||||
const parent = tile.parent;
|
||||
const parentRefines = !defined(parent) || parent._refines;
|
||||
|
||||
tile._refines = canTraverse(tile)
|
||||
? updateAndPushChildren(tile, stack, frameState) && parentRefines
|
||||
: false;
|
||||
|
||||
const stoppedRefining = !tile._refines && parentRefines;
|
||||
|
||||
if (!tile.hasRenderableContent) {
|
||||
// Add empty tile just to show its debug bounding volume
|
||||
// If the tile has tileset content load the external tileset
|
||||
// If the tile cannot refine further select its nearest loaded ancestor
|
||||
tileset._emptyTiles.push(tile);
|
||||
loadTile(tile, frameState);
|
||||
if (stoppedRefining) {
|
||||
selectDesiredTile(tile, frameState);
|
||||
}
|
||||
} else if (tile.refine === Cesium3DTileRefine.ADD) {
|
||||
// Additive tiles are always loaded and selected
|
||||
selectDesiredTile(tile, frameState);
|
||||
loadTile(tile, frameState);
|
||||
} else if (tile.refine === Cesium3DTileRefine.REPLACE) {
|
||||
if (inBaseTraversal(tile, baseScreenSpaceError)) {
|
||||
// Always load tiles in the base traversal
|
||||
// Select tiles that can't refine further
|
||||
loadTile(tile, frameState);
|
||||
if (stoppedRefining) {
|
||||
selectDesiredTile(tile, frameState);
|
||||
}
|
||||
} else if (stoppedRefining) {
|
||||
// In skip traversal, load and select tiles that can't refine further
|
||||
selectDesiredTile(tile, frameState);
|
||||
loadTile(tile, frameState);
|
||||
} else if (reachedSkippingThreshold(tileset, tile)) {
|
||||
// In skip traversal, load tiles that aren't skipped
|
||||
loadTile(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
visitTile(tile, frameState);
|
||||
touchTile(tile, frameState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the tree and check if their selected frame is the current frame. If so, add it to a selection queue.
|
||||
* This is a preorder traversal so children tiles are selected before ancestor tiles.
|
||||
*
|
||||
* The reason for the preorder traversal is so that tiles can easily be marked with their
|
||||
* selection depth. A tile's _selectionDepth is its depth in the tree where all non-selected tiles are removed.
|
||||
* This property is important for use in the stencil test because we want to render deeper tiles on top of their
|
||||
* ancestors. If a tileset is very deep, the depth is unlikely to fit into the stencil buffer.
|
||||
*
|
||||
* We want to select children before their ancestors because there is no guarantee on the relationship between
|
||||
* the children's z-depth and the ancestor's z-depth. We cannot rely on Z because we want the child to appear on top
|
||||
* of ancestor regardless of true depth. The stencil tests used require children to be drawn first.
|
||||
*
|
||||
* NOTE: 3D Tiles uses 3 bits from the stencil buffer meaning this will not work when there is a chain of
|
||||
* selected tiles that is deeper than 7. This is not very likely.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} root
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function traverseAndSelect(root, frameState) {
|
||||
const { selectTile, canTraverse } = Cesium3DTilesetTraversal;
|
||||
const { stack, ancestorStack } = selectionTraversal;
|
||||
let lastAncestor;
|
||||
|
||||
stack.push(root);
|
||||
|
||||
while (stack.length > 0 || ancestorStack.length > 0) {
|
||||
selectionTraversal.stackMaximumLength = Math.max(
|
||||
selectionTraversal.stackMaximumLength,
|
||||
stack.length,
|
||||
);
|
||||
selectionTraversal.ancestorStackMaximumLength = Math.max(
|
||||
selectionTraversal.ancestorStackMaximumLength,
|
||||
ancestorStack.length,
|
||||
);
|
||||
|
||||
if (ancestorStack.length > 0) {
|
||||
const waitingTile = ancestorStack.peek();
|
||||
if (waitingTile._stackLength === stack.length) {
|
||||
ancestorStack.pop();
|
||||
if (waitingTile !== lastAncestor) {
|
||||
waitingTile._finalResolution = false;
|
||||
}
|
||||
selectTile(waitingTile, frameState);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const tile = stack.pop();
|
||||
if (!defined(tile)) {
|
||||
// stack is empty but ancestorStack isn't
|
||||
continue;
|
||||
}
|
||||
|
||||
const traverse = canTraverse(tile);
|
||||
|
||||
if (tile._shouldSelect) {
|
||||
if (tile.refine === Cesium3DTileRefine.ADD) {
|
||||
selectTile(tile, frameState);
|
||||
} else {
|
||||
tile._selectionDepth = ancestorStack.length;
|
||||
if (tile._selectionDepth > 0) {
|
||||
tile.tileset.hasMixedContent = true;
|
||||
}
|
||||
lastAncestor = tile;
|
||||
if (!traverse) {
|
||||
selectTile(tile, frameState);
|
||||
continue;
|
||||
}
|
||||
ancestorStack.push(tile);
|
||||
tile._stackLength = stack.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (traverse) {
|
||||
const children = tile.children;
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
if (child.isVisible) {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Cesium3DTilesetSkipTraversal;
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import Model3DTileContent from "./Model/Model3DTileContent.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetStatistics() {
|
||||
// Rendering statistics
|
||||
this.selected = 0;
|
||||
this.visited = 0;
|
||||
// Loading statistics
|
||||
this.numberOfCommands = 0;
|
||||
this.numberOfAttemptedRequests = 0;
|
||||
this.numberOfPendingRequests = 0;
|
||||
this.numberOfTilesProcessing = 0;
|
||||
this.numberOfTilesWithContentReady = 0; // Number of tiles with content loaded, does not include empty tiles
|
||||
this.numberOfTilesTotal = 0; // Number of tiles in tileset JSON (and other tileset JSON files as they are loaded)
|
||||
this.numberOfLoadedTilesTotal = 0; // Running total of loaded tiles for the lifetime of the session
|
||||
// Features statistics
|
||||
this.numberOfFeaturesSelected = 0; // Number of features rendered
|
||||
this.numberOfFeaturesLoaded = 0; // Number of features in memory
|
||||
this.numberOfPointsSelected = 0;
|
||||
this.numberOfPointsLoaded = 0;
|
||||
this.numberOfTrianglesSelected = 0;
|
||||
// Styling statistics
|
||||
this.numberOfTilesStyled = 0;
|
||||
this.numberOfFeaturesStyled = 0;
|
||||
// Optimization statistics
|
||||
this.numberOfTilesCulledWithChildrenUnion = 0;
|
||||
// Memory statistics
|
||||
this.geometryByteLength = 0;
|
||||
this.texturesByteLength = 0;
|
||||
this.texturesReferenceCounterById = {};
|
||||
this.batchTableByteLength = 0; // batch textures and any binary metadata properties not otherwise accounted for
|
||||
}
|
||||
|
||||
Cesium3DTilesetStatistics.prototype.clear = function () {
|
||||
this.selected = 0;
|
||||
this.visited = 0;
|
||||
this.numberOfCommands = 0;
|
||||
this.numberOfAttemptedRequests = 0;
|
||||
this.numberOfFeaturesSelected = 0;
|
||||
this.numberOfPointsSelected = 0;
|
||||
this.numberOfTrianglesSelected = 0;
|
||||
this.numberOfTilesStyled = 0;
|
||||
this.numberOfFeaturesStyled = 0;
|
||||
this.numberOfTilesCulledWithChildrenUnion = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the counters for the points, triangles, and features
|
||||
* that are currently selected for rendering.
|
||||
*
|
||||
* This will be called recursively for the given content and
|
||||
* all its inner contents
|
||||
*
|
||||
* @param {Cesium3DTileContent} content
|
||||
*/
|
||||
Cesium3DTilesetStatistics.prototype.incrementSelectionCounts = function (
|
||||
content,
|
||||
) {
|
||||
this.numberOfFeaturesSelected += content.featuresLength;
|
||||
this.numberOfPointsSelected += content.pointsLength;
|
||||
this.numberOfTrianglesSelected += content.trianglesLength;
|
||||
|
||||
// Recursive calls on all inner contents
|
||||
const contents = content.innerContents;
|
||||
if (defined(contents)) {
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this.incrementSelectionCounts(contents[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the counters for the number of features and points that
|
||||
* are currently loaded, and the lengths (size in bytes) of the
|
||||
* occupied memory.
|
||||
*
|
||||
* This will be called recursively for the given content and
|
||||
* all its inner contents
|
||||
*
|
||||
* @param {Cesium3DTileContent} content
|
||||
*/
|
||||
Cesium3DTilesetStatistics.prototype.incrementLoadCounts = function (content) {
|
||||
this.numberOfFeaturesLoaded += content.featuresLength;
|
||||
this.numberOfPointsLoaded += content.pointsLength;
|
||||
this.geometryByteLength += content.geometryByteLength;
|
||||
this.batchTableByteLength += content.batchTableByteLength;
|
||||
|
||||
// When the content is not a `Model3DTileContent`, then its
|
||||
// textures byte length is added directly
|
||||
if (!(content instanceof Model3DTileContent)) {
|
||||
this.texturesByteLength += content.texturesByteLength;
|
||||
} else {
|
||||
// When the content is a `Model3DTileContent`, then increment the
|
||||
// reference counter for all its textures. The byte length of any
|
||||
// newly tracked texture to the total textures byte length
|
||||
const textureIds = content.getTextureIds();
|
||||
for (const textureId of textureIds) {
|
||||
const referenceCounter =
|
||||
this.texturesReferenceCounterById[textureId] ?? 0;
|
||||
if (referenceCounter === 0) {
|
||||
const textureByteLength = content.getTextureByteLengthById(textureId);
|
||||
this.texturesByteLength += textureByteLength;
|
||||
}
|
||||
this.texturesReferenceCounterById[textureId] = referenceCounter + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive calls on all inner contents
|
||||
const contents = content.innerContents;
|
||||
if (defined(contents)) {
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this.incrementLoadCounts(contents[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrement the counters for the number of features and points that
|
||||
* are currently loaded, and the lengths (size in bytes) of the
|
||||
* occupied memory.
|
||||
*
|
||||
* This will be called recursively for the given content and
|
||||
* all its inner contents
|
||||
*
|
||||
* @param {Cesium3DTileContent} content
|
||||
*/
|
||||
Cesium3DTilesetStatistics.prototype.decrementLoadCounts = function (content) {
|
||||
this.numberOfFeaturesLoaded -= content.featuresLength;
|
||||
this.numberOfPointsLoaded -= content.pointsLength;
|
||||
this.geometryByteLength -= content.geometryByteLength;
|
||||
this.batchTableByteLength -= content.batchTableByteLength;
|
||||
|
||||
// When the content is not a `Model3DTileContent`, then its
|
||||
// textures byte length is subtracted directly
|
||||
if (!(content instanceof Model3DTileContent)) {
|
||||
this.texturesByteLength -= content.texturesByteLength;
|
||||
} else {
|
||||
// When the content is a `Model3DTileContent`, then decrement the
|
||||
// reference counter for all its textures. The byte length of any
|
||||
// texture that is no longer references is subtracted from the
|
||||
// total textures byte length
|
||||
const textureIds = content.getTextureIds();
|
||||
for (const textureId of textureIds) {
|
||||
const referenceCounter = this.texturesReferenceCounterById[textureId];
|
||||
if (referenceCounter === 1) {
|
||||
delete this.texturesReferenceCounterById[textureId];
|
||||
const textureByteLength = content.getTextureByteLengthById(textureId);
|
||||
this.texturesByteLength -= textureByteLength;
|
||||
} else {
|
||||
this.texturesReferenceCounterById[textureId] = referenceCounter - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Recursive calls on all inner contents
|
||||
const contents = content.innerContents;
|
||||
if (defined(contents)) {
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this.decrementLoadCounts(contents[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Cesium3DTilesetStatistics.clone = function (statistics, result) {
|
||||
result.selected = statistics.selected;
|
||||
result.visited = statistics.visited;
|
||||
result.numberOfCommands = statistics.numberOfCommands;
|
||||
result.numberOfAttemptedRequests = statistics.numberOfAttemptedRequests;
|
||||
result.numberOfPendingRequests = statistics.numberOfPendingRequests;
|
||||
result.numberOfTilesProcessing = statistics.numberOfTilesProcessing;
|
||||
result.numberOfTilesWithContentReady =
|
||||
statistics.numberOfTilesWithContentReady;
|
||||
result.numberOfTilesTotal = statistics.numberOfTilesTotal;
|
||||
result.numberOfFeaturesSelected = statistics.numberOfFeaturesSelected;
|
||||
result.numberOfFeaturesLoaded = statistics.numberOfFeaturesLoaded;
|
||||
result.numberOfPointsSelected = statistics.numberOfPointsSelected;
|
||||
result.numberOfPointsLoaded = statistics.numberOfPointsLoaded;
|
||||
result.numberOfTrianglesSelected = statistics.numberOfTrianglesSelected;
|
||||
result.numberOfTilesStyled = statistics.numberOfTilesStyled;
|
||||
result.numberOfFeaturesStyled = statistics.numberOfFeaturesStyled;
|
||||
result.numberOfTilesCulledWithChildrenUnion =
|
||||
statistics.numberOfTilesCulledWithChildrenUnion;
|
||||
result.geometryByteLength = statistics.geometryByteLength;
|
||||
result.texturesByteLength = statistics.texturesByteLength;
|
||||
result.texturesReferenceCounterById = {
|
||||
...statistics.texturesReferenceCounterById,
|
||||
};
|
||||
result.batchTableByteLength = statistics.batchTableByteLength;
|
||||
};
|
||||
export default Cesium3DTilesetStatistics;
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Intersect from "../Core/Intersect.js";
|
||||
import Cesium3DTileOptimizationHint from "./Cesium3DTileOptimizationHint.js";
|
||||
import Cesium3DTileRefine from "./Cesium3DTileRefine.js";
|
||||
|
||||
/**
|
||||
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
||||
* This type describes an interface and is not intended to be instantiated directly.
|
||||
*
|
||||
* @alias Cesium3DTilesetTraversal
|
||||
* @constructor
|
||||
* @abstract
|
||||
*
|
||||
* @see Cesium3DTilesetBaseTraversal
|
||||
* @see Cesium3DTilesetSkipTraversal
|
||||
* @see Cesium3DTilesetMostDetailedTraversal
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function Cesium3DTilesetTraversal() {}
|
||||
|
||||
/**
|
||||
* Traverses a {@link Cesium3DTileset} to determine which tiles to load and render.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTileset} tileset
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.selectTiles = function (tileset, frameState) {
|
||||
DeveloperError.throwInstantiationError();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort by farthest child first since this is going on a stack
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} a
|
||||
* @param {Cesium3DTile} b
|
||||
* @returns {number}
|
||||
*/
|
||||
Cesium3DTilesetTraversal.sortChildrenByDistanceToCamera = function (a, b) {
|
||||
if (b._distanceToCamera === 0 && a._distanceToCamera === 0) {
|
||||
return b._centerZDepth - a._centerZDepth;
|
||||
}
|
||||
|
||||
return b._distanceToCamera - a._distanceToCamera;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a tile can and should be traversed for children tiles that
|
||||
* would contribute to rendering the current view
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Cesium3DTilesetTraversal.canTraverse = function (tile) {
|
||||
if (tile.children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (tile.hasTilesetContent || tile.hasImplicitContent) {
|
||||
// Traverse external tileset to visit its root tile
|
||||
// Don't traverse if the subtree is expired because it will be destroyed
|
||||
return !tile.contentExpired;
|
||||
}
|
||||
return tile._screenSpaceError > tile.tileset.memoryAdjustedScreenSpaceError;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark a tile as selected, and add it to the tileset's list of selected tiles
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.selectTile = function (tile, frameState) {
|
||||
if (tile.contentVisibility(frameState) === Intersect.OUTSIDE) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile._wasSelectedLastFrame = true;
|
||||
|
||||
const { content, tileset } = tile;
|
||||
if (content.featurePropertiesDirty) {
|
||||
// A feature's property in this tile changed, the tile needs to be re-styled.
|
||||
content.featurePropertiesDirty = false;
|
||||
tile.lastStyleTime = 0; // Force applying the style to this tile
|
||||
tileset._selectedTilesToStyle.push(tile);
|
||||
} else if (tile._selectedFrame < frameState.frameNumber - 1) {
|
||||
// Tile is newly selected; it is selected this frame, but was not selected last frame.
|
||||
tileset._selectedTilesToStyle.push(tile);
|
||||
tile._wasSelectedLastFrame = false;
|
||||
}
|
||||
|
||||
tile._selectedFrame = frameState.frameNumber;
|
||||
tileset._selectedTiles.push(tile);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.visitTile = function (tile, frameState) {
|
||||
++tile.tileset._statistics.visited;
|
||||
tile._visitedFrame = frameState.frameNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.touchTile = function (tile, frameState) {
|
||||
if (tile._touchedFrame === frameState.frameNumber) {
|
||||
// Prevents another pass from touching the frame again
|
||||
return;
|
||||
}
|
||||
tile.tileset._cache.touch(tile);
|
||||
tile._touchedFrame = frameState.frameNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a tile to the list of requested tiles, if appropriate
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.loadTile = function (tile, frameState) {
|
||||
const { tileset } = tile;
|
||||
if (
|
||||
tile._requestedFrame === frameState.frameNumber ||
|
||||
(!tile.hasUnloadedRenderableContent && !tile.contentExpired)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOnScreenLongEnough(tile, frameState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cameraHasNotStoppedMovingLongEnough =
|
||||
frameState.camera.timeSinceMoved < tileset.foveatedTimeDelay;
|
||||
if (tile.priorityDeferred && cameraHasNotStoppedMovingLongEnough) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile._requestedFrame = frameState.frameNumber;
|
||||
tileset._requestedTiles.push(tile);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevent unnecessary loads while camera is moving by getting the ratio of travel distance to tile size.
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isOnScreenLongEnough(tile, frameState) {
|
||||
const { tileset } = tile;
|
||||
if (!tileset._cullRequestsWhileMoving) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { positionWCDeltaMagnitude, positionWCDeltaMagnitudeLastFrame } =
|
||||
frameState.camera;
|
||||
const deltaMagnitude =
|
||||
positionWCDeltaMagnitude !== 0.0
|
||||
? positionWCDeltaMagnitude
|
||||
: positionWCDeltaMagnitudeLastFrame;
|
||||
|
||||
// How do n frames of this movement compare to the tile's physical size.
|
||||
const diameter = Math.max(tile.boundingSphere.radius * 2.0, 1.0);
|
||||
const movementRatio =
|
||||
(tileset.cullRequestsWhileMovingMultiplier * deltaMagnitude) / diameter;
|
||||
|
||||
return movementRatio < 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some of the tile's flags and re-evaluate visibility and priority
|
||||
*
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
Cesium3DTilesetTraversal.updateTile = function (tile, frameState) {
|
||||
updateTileVisibility(tile, frameState);
|
||||
tile.updateExpiration();
|
||||
|
||||
tile._wasMinPriorityChild = false;
|
||||
tile._priorityHolder = tile;
|
||||
updateMinimumMaximumPriority(tile);
|
||||
|
||||
// SkipLOD
|
||||
tile._shouldSelect = false;
|
||||
tile._finalResolution = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
*/
|
||||
function updateTileVisibility(tile, frameState) {
|
||||
tile.updateVisibility(frameState);
|
||||
|
||||
if (!tile.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasChildren = tile.children.length > 0;
|
||||
if ((tile.hasTilesetContent || tile.hasImplicitContent) && hasChildren) {
|
||||
// Use the root tile's visibility instead of this tile's visibility.
|
||||
// The root tile may be culled by the children bounds optimization in which
|
||||
// case this tile should also be culled.
|
||||
const child = tile.children[0];
|
||||
updateTileVisibility(child, frameState);
|
||||
tile._visible = child._visible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (meetsScreenSpaceErrorEarly(tile, frameState)) {
|
||||
tile._visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization - if none of the tile's children are visible then this tile isn't visible
|
||||
const replace = tile.refine === Cesium3DTileRefine.REPLACE;
|
||||
const useOptimization =
|
||||
tile._optimChildrenWithinParent ===
|
||||
Cesium3DTileOptimizationHint.USE_OPTIMIZATION;
|
||||
if (replace && useOptimization && hasChildren) {
|
||||
if (!anyChildrenVisible(tile, frameState)) {
|
||||
++tile.tileset._statistics.numberOfTilesCulledWithChildrenUnion;
|
||||
tile._visible = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function meetsScreenSpaceErrorEarly(tile, frameState) {
|
||||
const { parent, tileset } = tile;
|
||||
if (
|
||||
!defined(parent) ||
|
||||
parent.hasTilesetContent ||
|
||||
parent.hasImplicitContent ||
|
||||
parent.refine !== Cesium3DTileRefine.ADD
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use parent's geometric error with child's box to see if the tile already meet the SSE
|
||||
return (
|
||||
tile.getScreenSpaceError(frameState, true) <=
|
||||
tileset.memoryAdjustedScreenSpaceError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
* @param {FrameState} frameState
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function anyChildrenVisible(tile, frameState) {
|
||||
let anyVisible = false;
|
||||
const children = tile.children;
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
const child = children[i];
|
||||
child.updateVisibility(frameState);
|
||||
anyVisible = anyVisible || child.isVisible;
|
||||
}
|
||||
return anyVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Cesium3DTile} tile
|
||||
*/
|
||||
function updateMinimumMaximumPriority(tile) {
|
||||
const minimumPriority = tile.tileset._minimumPriority;
|
||||
const maximumPriority = tile.tileset._maximumPriority;
|
||||
const priorityHolder = tile._priorityHolder;
|
||||
|
||||
maximumPriority.distance = Math.max(
|
||||
priorityHolder._distanceToCamera,
|
||||
maximumPriority.distance,
|
||||
);
|
||||
minimumPriority.distance = Math.min(
|
||||
priorityHolder._distanceToCamera,
|
||||
minimumPriority.distance,
|
||||
);
|
||||
maximumPriority.depth = Math.max(tile._depth, maximumPriority.depth);
|
||||
minimumPriority.depth = Math.min(tile._depth, minimumPriority.depth);
|
||||
maximumPriority.foveatedFactor = Math.max(
|
||||
priorityHolder._foveatedFactor,
|
||||
maximumPriority.foveatedFactor,
|
||||
);
|
||||
minimumPriority.foveatedFactor = Math.min(
|
||||
priorityHolder._foveatedFactor,
|
||||
minimumPriority.foveatedFactor,
|
||||
);
|
||||
maximumPriority.reverseScreenSpaceError = Math.max(
|
||||
tile._priorityReverseScreenSpaceError,
|
||||
maximumPriority.reverseScreenSpaceError,
|
||||
);
|
||||
minimumPriority.reverseScreenSpaceError = Math.min(
|
||||
tile._priorityReverseScreenSpaceError,
|
||||
minimumPriority.reverseScreenSpaceError,
|
||||
);
|
||||
}
|
||||
|
||||
export default Cesium3DTilesetTraversal;
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
/**
|
||||
* A ParticleEmitter that emits particles from a circle.
|
||||
* Particles will be positioned within a circle and have initial velocities going along the z vector.
|
||||
*
|
||||
* @alias CircleEmitter
|
||||
* @constructor
|
||||
*
|
||||
* @param {number} [radius=1.0] The radius of the circle in meters.
|
||||
*/
|
||||
function CircleEmitter(radius) {
|
||||
radius = radius ?? 1.0;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number.greaterThan("radius", radius, 0.0);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._radius = radius ?? 1.0;
|
||||
}
|
||||
|
||||
Object.defineProperties(CircleEmitter.prototype, {
|
||||
/**
|
||||
* The radius of the circle in meters.
|
||||
* @memberof CircleEmitter.prototype
|
||||
* @type {number}
|
||||
* @default 1.0
|
||||
*/
|
||||
radius: {
|
||||
get: function () {
|
||||
return this._radius;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number.greaterThan("value", value, 0.0);
|
||||
//>>includeEnd('debug');
|
||||
this._radius = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the given {@link Particle} by setting it's position and velocity.
|
||||
*
|
||||
* @private
|
||||
* @param {Particle} particle The particle to initialize.
|
||||
*/
|
||||
CircleEmitter.prototype.emit = function (particle) {
|
||||
const theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI);
|
||||
const rad = CesiumMath.randomBetween(0.0, this._radius);
|
||||
|
||||
const x = rad * Math.cos(theta);
|
||||
const y = rad * Math.sin(theta);
|
||||
const z = 0.0;
|
||||
|
||||
particle.position = Cartesian3.fromElements(x, y, z, particle.position);
|
||||
particle.velocity = Cartesian3.clone(Cartesian3.UNIT_Z, particle.velocity);
|
||||
};
|
||||
export default CircleEmitter;
|
||||
+1387
File diff suppressed because it is too large
Load Diff
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Whether a classification affects terrain, 3D Tiles or both.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const ClassificationType = {
|
||||
/**
|
||||
* Only terrain will be classified.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
TERRAIN: 0,
|
||||
/**
|
||||
* Only 3D Tiles will be classified.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
CESIUM_3D_TILE: 1,
|
||||
/**
|
||||
* Both terrain and 3D Tiles will be classified.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
BOTH: 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ClassificationType.NUMBER_OF_CLASSIFICATION_TYPES = 3;
|
||||
|
||||
Object.freeze(ClassificationType);
|
||||
|
||||
export default ClassificationType;
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/**
|
||||
* A Plane in Hessian Normal form to be used with {@link ClippingPlaneCollection}.
|
||||
* Compatible with mathematics functions in {@link Plane}
|
||||
*
|
||||
* @alias ClippingPlane
|
||||
* @constructor
|
||||
*
|
||||
* @param {Cartesian3} normal The plane's normal (normalized).
|
||||
* @param {number} distance The shortest distance from the origin to the plane. The sign of
|
||||
* <code>distance</code> determines which side of the plane the origin
|
||||
* is on. If <code>distance</code> is positive, the origin is in the half-space
|
||||
* in the direction of the normal; if negative, the origin is in the half-space
|
||||
* opposite to the normal; if zero, the plane passes through the origin.
|
||||
*/
|
||||
function ClippingPlane(normal, distance) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("normal", normal);
|
||||
Check.typeOf.number("distance", distance);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._distance = distance;
|
||||
this._normal = new UpdateChangedCartesian3(normal, this);
|
||||
this.onChangeCallback = undefined;
|
||||
this.index = -1; // to be set by ClippingPlaneCollection
|
||||
}
|
||||
|
||||
Object.defineProperties(ClippingPlane.prototype, {
|
||||
/**
|
||||
* The shortest distance from the origin to the plane. The sign of
|
||||
* <code>distance</code> determines which side of the plane the origin
|
||||
* is on. If <code>distance</code> is positive, the origin is in the half-space
|
||||
* in the direction of the normal; if negative, the origin is in the half-space
|
||||
* opposite to the normal; if zero, the plane passes through the origin.
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ClippingPlane.prototype
|
||||
*/
|
||||
distance: {
|
||||
get: function () {
|
||||
return this._distance;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (defined(this.onChangeCallback) && value !== this._distance) {
|
||||
this.onChangeCallback(this.index);
|
||||
}
|
||||
this._distance = value;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The plane's normal.
|
||||
*
|
||||
* @type {Cartesian3}
|
||||
* @memberof ClippingPlane.prototype
|
||||
*/
|
||||
normal: {
|
||||
get: function () {
|
||||
return this._normal;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (
|
||||
defined(this.onChangeCallback) &&
|
||||
!Cartesian3.equals(this._normal._cartesian3, value)
|
||||
) {
|
||||
this.onChangeCallback(this.index);
|
||||
}
|
||||
// Set without firing callback again
|
||||
Cartesian3.clone(value, this._normal._cartesian3);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a ClippingPlane from a Plane object.
|
||||
*
|
||||
* @param {Plane} plane The plane containing parameters to copy
|
||||
* @param {ClippingPlane} [result] The object on which to store the result
|
||||
* @returns {ClippingPlane} The ClippingPlane generated from the plane's parameters.
|
||||
*/
|
||||
ClippingPlane.fromPlane = function (plane, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("plane", plane);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(result)) {
|
||||
result = new ClippingPlane(plane.normal, plane.distance);
|
||||
} else {
|
||||
result.normal = plane.normal;
|
||||
result.distance = plane.distance;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones the ClippingPlane without setting its ownership.
|
||||
* @param {ClippingPlane} clippingPlane The ClippingPlane to be cloned
|
||||
* @param {ClippingPlane} [result] The object on which to store the cloned parameters.
|
||||
* @returns {ClippingPlane} a clone of the input ClippingPlane
|
||||
*/
|
||||
ClippingPlane.clone = function (clippingPlane, result) {
|
||||
if (!defined(result)) {
|
||||
return new ClippingPlane(clippingPlane.normal, clippingPlane.distance);
|
||||
}
|
||||
result.normal = clippingPlane.normal;
|
||||
result.distance = clippingPlane.distance;
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper on Cartesian3 that allows detection of Plane changes from "members of members," for example:
|
||||
*
|
||||
* const clippingPlane = new ClippingPlane(...);
|
||||
* clippingPlane.normal.z = -1.0;
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function UpdateChangedCartesian3(normal, clippingPlane) {
|
||||
this._clippingPlane = clippingPlane;
|
||||
this._cartesian3 = Cartesian3.clone(normal);
|
||||
}
|
||||
|
||||
Object.defineProperties(UpdateChangedCartesian3.prototype, {
|
||||
x: {
|
||||
get: function () {
|
||||
return this._cartesian3.x;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (
|
||||
defined(this._clippingPlane.onChangeCallback) &&
|
||||
value !== this._cartesian3.x
|
||||
) {
|
||||
this._clippingPlane.onChangeCallback(this._clippingPlane.index);
|
||||
}
|
||||
this._cartesian3.x = value;
|
||||
},
|
||||
},
|
||||
y: {
|
||||
get: function () {
|
||||
return this._cartesian3.y;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (
|
||||
defined(this._clippingPlane.onChangeCallback) &&
|
||||
value !== this._cartesian3.y
|
||||
) {
|
||||
this._clippingPlane.onChangeCallback(this._clippingPlane.index);
|
||||
}
|
||||
this._cartesian3.y = value;
|
||||
},
|
||||
},
|
||||
z: {
|
||||
get: function () {
|
||||
return this._cartesian3.z;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
if (
|
||||
defined(this._clippingPlane.onChangeCallback) &&
|
||||
value !== this._cartesian3.z
|
||||
) {
|
||||
this._clippingPlane.onChangeCallback(this._clippingPlane.index);
|
||||
}
|
||||
this._cartesian3.z = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
export default ClippingPlane;
|
||||
+764
@@ -0,0 +1,764 @@
|
||||
import AttributeCompression from "../Core/AttributeCompression.js";
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartesian4 from "../Core/Cartesian4.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Event from "../Core/Event.js";
|
||||
import Intersect from "../Core/Intersect.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import PixelFormat from "../Core/PixelFormat.js";
|
||||
import Plane from "../Core/Plane.js";
|
||||
import ContextLimits from "../Renderer/ContextLimits.js";
|
||||
import PixelDatatype from "../Renderer/PixelDatatype.js";
|
||||
import Sampler from "../Renderer/Sampler.js";
|
||||
import Texture from "../Renderer/Texture.js";
|
||||
import ClippingPlane from "./ClippingPlane.js";
|
||||
|
||||
/**
|
||||
* Specifies a set of clipping planes. Clipping planes selectively disable rendering in a region on the
|
||||
* outside of the specified list of {@link ClippingPlane} objects for a single gltf model, 3D Tileset, or the globe.
|
||||
* <p>
|
||||
* In general the clipping planes' coordinates are relative to the object they're attached to, so a plane with distance set to 0 will clip
|
||||
* through the center of the object.
|
||||
* </p>
|
||||
* <p>
|
||||
* For 3D Tiles, the root tile's transform is used to position the clipping planes. If a transform is not defined, the root tile's {@link Cesium3DTile#boundingSphere} is used instead.
|
||||
* </p>
|
||||
*
|
||||
* @alias ClippingPlaneCollection
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} [options] Object with the following properties:
|
||||
* @param {ClippingPlane[]} [options.planes=[]] An array of {@link ClippingPlane} objects used to selectively disable rendering on the outside of each plane.
|
||||
* @param {boolean} [options.enabled=true] Determines whether the clipping planes are active.
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix specifying an additional transform relative to the clipping planes original coordinate system.
|
||||
* @param {boolean} [options.unionClippingRegions=false] If true, a region will be clipped if it is on the outside of any plane in the collection. Otherwise, a region will only be clipped if it is on the outside of every plane.
|
||||
* @param {Color} [options.edgeColor=Color.WHITE] The color applied to highlight the edge along which an object is clipped.
|
||||
* @param {number} [options.edgeWidth=0.0] The width, in pixels, of the highlight applied to the edge along which an object is clipped.
|
||||
*
|
||||
* @demo {@link https://sandcastle.cesium.com/?id=3d-tiles-clipping-planes|Clipping 3D Tiles and glTF models.}
|
||||
* @demo {@link https://sandcastle.cesium.com/?id=terrain-clipping-planes|Clipping the Globe.}
|
||||
*
|
||||
* @example
|
||||
* // This clipping plane's distance is positive, which means its normal
|
||||
* // is facing the origin. This will clip everything that is behind
|
||||
* // the plane, which is anything with y coordinate < -5.
|
||||
* const clippingPlanes = new Cesium.ClippingPlaneCollection({
|
||||
* planes : [
|
||||
* new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), 5.0)
|
||||
* ],
|
||||
* });
|
||||
* // Create an entity and attach the ClippingPlaneCollection to the model.
|
||||
* const entity = viewer.entities.add({
|
||||
* position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 10000),
|
||||
* model : {
|
||||
* uri : 'model.gltf',
|
||||
* minimumPixelSize : 128,
|
||||
* maximumScale : 20000,
|
||||
* clippingPlanes : clippingPlanes
|
||||
* }
|
||||
* });
|
||||
* viewer.zoomTo(entity);
|
||||
*/
|
||||
function ClippingPlaneCollection(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
this._planes = [];
|
||||
|
||||
// Do partial texture updates if just one plane is dirty.
|
||||
// If many planes are dirty, refresh the entire texture.
|
||||
this._dirtyIndex = -1;
|
||||
this._multipleDirtyPlanes = false;
|
||||
|
||||
this._enabled = options.enabled ?? true;
|
||||
|
||||
/**
|
||||
* The 4x4 transformation matrix specifying an additional transform relative to the clipping planes
|
||||
* original coordinate system.
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @default Matrix4.IDENTITY
|
||||
*/
|
||||
this.modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
|
||||
|
||||
/**
|
||||
* The color applied to highlight the edge along which an object is clipped.
|
||||
*
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
this.edgeColor = Color.clone(options.edgeColor ?? Color.WHITE);
|
||||
|
||||
/**
|
||||
* The width, in pixels, of the highlight applied to the edge along which an object is clipped.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0.0
|
||||
*/
|
||||
this.edgeWidth = options.edgeWidth ?? 0.0;
|
||||
|
||||
/**
|
||||
* An event triggered when a new clipping plane is added to the collection. Event handlers
|
||||
* are passed the new plane and the index at which it was added.
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
this.planeAdded = new Event();
|
||||
|
||||
/**
|
||||
* An event triggered when a new clipping plane is removed from the collection. Event handlers
|
||||
* are passed the new plane and the index from which it was removed.
|
||||
* @type {Event}
|
||||
* @readonly
|
||||
*/
|
||||
this.planeRemoved = new Event();
|
||||
|
||||
// If this ClippingPlaneCollection has an owner, only its owner should update or destroy it.
|
||||
// This is because in a Cesium3DTileset multiple models may reference the tileset's ClippingPlaneCollection.
|
||||
this._owner = undefined;
|
||||
|
||||
const unionClippingRegions = options.unionClippingRegions ?? false;
|
||||
this._unionClippingRegions = unionClippingRegions;
|
||||
this._testIntersection = unionClippingRegions
|
||||
? unionIntersectFunction
|
||||
: defaultIntersectFunction;
|
||||
|
||||
this._uint8View = undefined;
|
||||
this._float32View = undefined;
|
||||
|
||||
this._clippingPlanesTexture = undefined;
|
||||
|
||||
// Add each ClippingPlane object.
|
||||
const planes = options.planes;
|
||||
if (defined(planes)) {
|
||||
const planesLength = planes.length;
|
||||
for (let i = 0; i < planesLength; ++i) {
|
||||
this.add(planes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unionIntersectFunction(value) {
|
||||
return value === Intersect.OUTSIDE;
|
||||
}
|
||||
|
||||
function defaultIntersectFunction(value) {
|
||||
return value === Intersect.INSIDE;
|
||||
}
|
||||
|
||||
Object.defineProperties(ClippingPlaneCollection.prototype, {
|
||||
/**
|
||||
* Returns the number of planes in this collection. This is commonly used with
|
||||
* {@link ClippingPlaneCollection#get} to iterate over all the planes
|
||||
* in the collection.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
length: {
|
||||
get: function () {
|
||||
return this._planes.length;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, a region will be clipped if it is on the outside of any plane in the
|
||||
* collection. Otherwise, a region will only be clipped if it is on the
|
||||
* outside of every plane.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
unionClippingRegions: {
|
||||
get: function () {
|
||||
return this._unionClippingRegions;
|
||||
},
|
||||
set: function (value) {
|
||||
if (this._unionClippingRegions === value) {
|
||||
return;
|
||||
}
|
||||
this._unionClippingRegions = value;
|
||||
this._testIntersection = value
|
||||
? unionIntersectFunction
|
||||
: defaultIntersectFunction;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, clipping will be enabled.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
enabled: {
|
||||
get: function () {
|
||||
return this._enabled;
|
||||
},
|
||||
set: function (value) {
|
||||
if (this._enabled === value) {
|
||||
return;
|
||||
}
|
||||
this._enabled = value;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a texture containing packed, untransformed clipping planes.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @type {Texture}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
texture: {
|
||||
get: function () {
|
||||
return this._clippingPlanesTexture;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A reference to the ClippingPlaneCollection's owner, if any.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
owner: {
|
||||
get: function () {
|
||||
return this._owner;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Number encapsulating the state for this ClippingPlaneCollection.
|
||||
*
|
||||
* Clipping mode is encoded in the sign of the number, which is just the plane count.
|
||||
* If this value changes, then shader regeneration is necessary.
|
||||
*
|
||||
* @memberof ClippingPlaneCollection.prototype
|
||||
* @returns {number} A Number that describes the ClippingPlaneCollection's state.
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
clippingPlanesState: {
|
||||
get: function () {
|
||||
return this._unionClippingRegions
|
||||
? this._planes.length
|
||||
: -this._planes.length;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function setIndexDirty(collection, index) {
|
||||
// If there's already a different _dirtyIndex set, more than one plane has changed since update.
|
||||
// Entire texture must be reloaded
|
||||
collection._multipleDirtyPlanes =
|
||||
collection._multipleDirtyPlanes ||
|
||||
(collection._dirtyIndex !== -1 && collection._dirtyIndex !== index);
|
||||
collection._dirtyIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified {@link ClippingPlane} to the collection to be used to selectively disable rendering
|
||||
* on the outside of each plane. Use {@link ClippingPlaneCollection#unionClippingRegions} to modify
|
||||
* how modify the clipping behavior of multiple planes.
|
||||
*
|
||||
* @param {ClippingPlane} plane The ClippingPlane to add to the collection.
|
||||
*
|
||||
* @see ClippingPlaneCollection#unionClippingRegions
|
||||
* @see ClippingPlaneCollection#remove
|
||||
* @see ClippingPlaneCollection#removeAll
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.add = function (plane) {
|
||||
const newPlaneIndex = this._planes.length;
|
||||
|
||||
const that = this;
|
||||
plane.onChangeCallback = function (index) {
|
||||
setIndexDirty(that, index);
|
||||
};
|
||||
plane.index = newPlaneIndex;
|
||||
|
||||
setIndexDirty(this, newPlaneIndex);
|
||||
this._planes.push(plane);
|
||||
this.planeAdded.raiseEvent(plane, newPlaneIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the plane in the collection at the specified index. Indices are zero-based
|
||||
* and increase as planes are added. Removing a plane shifts all planes after
|
||||
* it to the left, changing their indices. This function is commonly used with
|
||||
* {@link ClippingPlaneCollection#length} to iterate over all the planes
|
||||
* in the collection.
|
||||
*
|
||||
* @param {number} index The zero-based index of the plane.
|
||||
* @returns {ClippingPlane} The ClippingPlane at the specified index.
|
||||
*
|
||||
* @see ClippingPlaneCollection#length
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.get = function (index) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("index", index);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
return this._planes[index];
|
||||
};
|
||||
|
||||
function indexOf(planes, plane) {
|
||||
const length = planes.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (Plane.equals(planes[i], plane)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this collection contains a ClippingPlane equal to the given ClippingPlane.
|
||||
*
|
||||
* @param {ClippingPlane} [clippingPlane] The ClippingPlane to check for.
|
||||
* @returns {boolean} true if this collection contains the ClippingPlane, false otherwise.
|
||||
*
|
||||
* @see ClippingPlaneCollection#get
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.contains = function (clippingPlane) {
|
||||
return indexOf(this._planes, clippingPlane) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the first occurrence of the given ClippingPlane from the collection.
|
||||
*
|
||||
* @param {ClippingPlane} clippingPlane
|
||||
* @returns {boolean} <code>true</code> if the plane was removed; <code>false</code> if the plane was not found in the collection.
|
||||
*
|
||||
* @see ClippingPlaneCollection#add
|
||||
* @see ClippingPlaneCollection#contains
|
||||
* @see ClippingPlaneCollection#removeAll
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.remove = function (clippingPlane) {
|
||||
const planes = this._planes;
|
||||
const index = indexOf(planes, clippingPlane);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unlink this ClippingPlaneCollection from the ClippingPlane
|
||||
if (clippingPlane instanceof ClippingPlane) {
|
||||
clippingPlane.onChangeCallback = undefined;
|
||||
clippingPlane.index = -1;
|
||||
}
|
||||
|
||||
// Shift and update indices
|
||||
const length = planes.length - 1;
|
||||
for (let i = index; i < length; ++i) {
|
||||
const planeToKeep = planes[i + 1];
|
||||
planes[i] = planeToKeep;
|
||||
if (planeToKeep instanceof ClippingPlane) {
|
||||
planeToKeep.index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate planes texture is dirty
|
||||
this._multipleDirtyPlanes = true;
|
||||
planes.length = length;
|
||||
|
||||
this.planeRemoved.raiseEvent(clippingPlane, index);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all planes from the collection.
|
||||
*
|
||||
* @see ClippingPlaneCollection#add
|
||||
* @see ClippingPlaneCollection#remove
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.removeAll = function () {
|
||||
// Dereference this ClippingPlaneCollection from all ClippingPlanes
|
||||
const planes = this._planes;
|
||||
const planesCount = planes.length;
|
||||
for (let i = 0; i < planesCount; ++i) {
|
||||
const plane = planes[i];
|
||||
if (plane instanceof ClippingPlane) {
|
||||
plane.onChangeCallback = undefined;
|
||||
plane.index = -1;
|
||||
}
|
||||
this.planeRemoved.raiseEvent(plane, i);
|
||||
}
|
||||
this._multipleDirtyPlanes = true;
|
||||
this._planes = [];
|
||||
};
|
||||
|
||||
const distanceEncodeScratch = new Cartesian4();
|
||||
const oct32EncodeScratch = new Cartesian4();
|
||||
function packPlanesAsUint8(clippingPlaneCollection, startIndex, endIndex) {
|
||||
const uint8View = clippingPlaneCollection._uint8View;
|
||||
const planes = clippingPlaneCollection._planes;
|
||||
let byteIndex = 0;
|
||||
for (let i = startIndex; i < endIndex; ++i) {
|
||||
const plane = planes[i];
|
||||
|
||||
const oct32Normal = AttributeCompression.octEncodeToCartesian4(
|
||||
plane.normal,
|
||||
oct32EncodeScratch,
|
||||
);
|
||||
uint8View[byteIndex] = oct32Normal.x;
|
||||
uint8View[byteIndex + 1] = oct32Normal.y;
|
||||
uint8View[byteIndex + 2] = oct32Normal.z;
|
||||
uint8View[byteIndex + 3] = oct32Normal.w;
|
||||
|
||||
const encodedDistance = Cartesian4.packFloat(
|
||||
plane.distance,
|
||||
distanceEncodeScratch,
|
||||
);
|
||||
uint8View[byteIndex + 4] = encodedDistance.x;
|
||||
uint8View[byteIndex + 5] = encodedDistance.y;
|
||||
uint8View[byteIndex + 6] = encodedDistance.z;
|
||||
uint8View[byteIndex + 7] = encodedDistance.w;
|
||||
|
||||
byteIndex += 8;
|
||||
}
|
||||
}
|
||||
|
||||
// Pack starting at the beginning of the buffer to allow partial update
|
||||
function packPlanesAsFloats(clippingPlaneCollection, startIndex, endIndex) {
|
||||
const float32View = clippingPlaneCollection._float32View;
|
||||
const planes = clippingPlaneCollection._planes;
|
||||
|
||||
let floatIndex = 0;
|
||||
for (let i = startIndex; i < endIndex; ++i) {
|
||||
const plane = planes[i];
|
||||
const normal = plane.normal;
|
||||
|
||||
float32View[floatIndex] = normal.x;
|
||||
float32View[floatIndex + 1] = normal.y;
|
||||
float32View[floatIndex + 2] = normal.z;
|
||||
float32View[floatIndex + 3] = plane.distance;
|
||||
|
||||
floatIndex += 4; // each plane is 4 floats
|
||||
}
|
||||
}
|
||||
|
||||
function computeTextureResolution(pixelsNeeded, result) {
|
||||
const maxSize = ContextLimits.maximumTextureSize;
|
||||
result.x = Math.min(pixelsNeeded, maxSize);
|
||||
result.y = Math.ceil(pixelsNeeded / result.x);
|
||||
return result;
|
||||
}
|
||||
|
||||
const textureResolutionScratch = new Cartesian2();
|
||||
/**
|
||||
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
|
||||
* build the resources for clipping planes.
|
||||
* <p>
|
||||
* Do not call this function directly.
|
||||
* </p>
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.update = function (frameState) {
|
||||
let clippingPlanesTexture = this._clippingPlanesTexture;
|
||||
const context = frameState.context;
|
||||
const useFloatTexture = ClippingPlaneCollection.useFloatTexture(context);
|
||||
|
||||
// Compute texture requirements for current planes
|
||||
// In RGBA FLOAT, A plane is 4 floats packed to a RGBA.
|
||||
// In RGBA UNSIGNED_BYTE, A plane is a float in [0, 1) packed to RGBA and an Oct32 quantized normal,
|
||||
// so 8 bytes or 2 pixels in RGBA.
|
||||
const pixelsNeeded = useFloatTexture ? this.length : this.length * 2;
|
||||
|
||||
if (defined(clippingPlanesTexture)) {
|
||||
const currentPixelCount =
|
||||
clippingPlanesTexture.width * clippingPlanesTexture.height;
|
||||
// Recreate the texture to double current requirement if it isn't big enough or is 4 times larger than it needs to be.
|
||||
// Optimization note: this isn't exactly the classic resizeable array algorithm
|
||||
// * not necessarily checking for resize after each add/remove operation
|
||||
// * random-access deletes instead of just pops
|
||||
// * alloc ops likely more expensive than demonstrable via big-O analysis
|
||||
if (
|
||||
currentPixelCount < pixelsNeeded ||
|
||||
pixelsNeeded < 0.25 * currentPixelCount
|
||||
) {
|
||||
clippingPlanesTexture.destroy();
|
||||
clippingPlanesTexture = undefined;
|
||||
this._clippingPlanesTexture = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no clipping planes, there's nothing to update.
|
||||
if (this.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined(clippingPlanesTexture)) {
|
||||
const requiredResolution = computeTextureResolution(
|
||||
pixelsNeeded,
|
||||
textureResolutionScratch,
|
||||
);
|
||||
// Allocate twice as much space as needed to avoid frequent texture reallocation.
|
||||
// Allocate in the Y direction, since texture may be as wide as context texture support.
|
||||
requiredResolution.y *= 2;
|
||||
|
||||
if (useFloatTexture) {
|
||||
clippingPlanesTexture = new Texture({
|
||||
context: context,
|
||||
width: requiredResolution.x,
|
||||
height: requiredResolution.y,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: PixelDatatype.FLOAT,
|
||||
sampler: Sampler.NEAREST,
|
||||
flipY: false,
|
||||
});
|
||||
this._float32View = new Float32Array(
|
||||
requiredResolution.x * requiredResolution.y * 4,
|
||||
);
|
||||
} else {
|
||||
clippingPlanesTexture = new Texture({
|
||||
context: context,
|
||||
width: requiredResolution.x,
|
||||
height: requiredResolution.y,
|
||||
pixelFormat: PixelFormat.RGBA,
|
||||
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
||||
sampler: Sampler.NEAREST,
|
||||
flipY: false,
|
||||
});
|
||||
this._uint8View = new Uint8Array(
|
||||
requiredResolution.x * requiredResolution.y * 4,
|
||||
);
|
||||
}
|
||||
|
||||
this._clippingPlanesTexture = clippingPlanesTexture;
|
||||
this._multipleDirtyPlanes = true;
|
||||
}
|
||||
|
||||
const dirtyIndex = this._dirtyIndex;
|
||||
if (!this._multipleDirtyPlanes && dirtyIndex === -1) {
|
||||
return;
|
||||
}
|
||||
if (!this._multipleDirtyPlanes) {
|
||||
// partial updates possible
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
if (useFloatTexture) {
|
||||
offsetY = Math.floor(dirtyIndex / clippingPlanesTexture.width);
|
||||
offsetX = Math.floor(dirtyIndex - offsetY * clippingPlanesTexture.width);
|
||||
|
||||
packPlanesAsFloats(this, dirtyIndex, dirtyIndex + 1);
|
||||
clippingPlanesTexture.copyFrom({
|
||||
source: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
arrayBufferView: this._float32View,
|
||||
},
|
||||
xOffset: offsetX,
|
||||
yOffset: offsetY,
|
||||
});
|
||||
} else {
|
||||
offsetY = Math.floor((dirtyIndex * 2) / clippingPlanesTexture.width);
|
||||
offsetX = Math.floor(
|
||||
dirtyIndex * 2 - offsetY * clippingPlanesTexture.width,
|
||||
);
|
||||
packPlanesAsUint8(this, dirtyIndex, dirtyIndex + 1);
|
||||
clippingPlanesTexture.copyFrom({
|
||||
source: {
|
||||
width: 2,
|
||||
height: 1,
|
||||
arrayBufferView: this._uint8View,
|
||||
},
|
||||
xOffset: offsetX,
|
||||
yOffset: offsetY,
|
||||
});
|
||||
}
|
||||
} else if (useFloatTexture) {
|
||||
packPlanesAsFloats(this, 0, this._planes.length);
|
||||
clippingPlanesTexture.copyFrom({
|
||||
source: {
|
||||
width: clippingPlanesTexture.width,
|
||||
height: clippingPlanesTexture.height,
|
||||
arrayBufferView: this._float32View,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
packPlanesAsUint8(this, 0, this._planes.length);
|
||||
clippingPlanesTexture.copyFrom({
|
||||
source: {
|
||||
width: clippingPlanesTexture.width,
|
||||
height: clippingPlanesTexture.height,
|
||||
arrayBufferView: this._uint8View,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this._multipleDirtyPlanes = false;
|
||||
this._dirtyIndex = -1;
|
||||
};
|
||||
|
||||
const scratchMatrix = new Matrix4();
|
||||
const scratchPlane = new Plane(Cartesian3.UNIT_X, 0.0);
|
||||
/**
|
||||
* Determines the type intersection with the planes of this ClippingPlaneCollection instance and the specified {@link TileBoundingVolume}.
|
||||
* @private
|
||||
*
|
||||
* @param {object} tileBoundingVolume The volume to determine the intersection with the planes.
|
||||
* @param {Matrix4} [transform] An optional, additional matrix to transform the plane to world coordinates.
|
||||
* @returns {Intersect} {@link Intersect.INSIDE} if the entire volume is on the side of the planes
|
||||
* the normal is pointing and should be entirely rendered, {@link Intersect.OUTSIDE}
|
||||
* if the entire volume is on the opposite side and should be clipped, and
|
||||
* {@link Intersect.INTERSECTING} if the volume intersects the planes.
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.computeIntersectionWithBoundingVolume =
|
||||
function (tileBoundingVolume, transform) {
|
||||
const planes = this._planes;
|
||||
const length = planes.length;
|
||||
|
||||
let modelMatrix = this.modelMatrix;
|
||||
if (defined(transform)) {
|
||||
modelMatrix = Matrix4.multiply(transform, modelMatrix, scratchMatrix);
|
||||
}
|
||||
|
||||
// If the collection is not set to union the clipping regions, the volume must be outside of all planes to be
|
||||
// considered completely clipped. If the collection is set to union the clipping regions, if the volume can be
|
||||
// outside any the planes, it is considered completely clipped.
|
||||
// Lastly, if not completely clipped, if any plane is intersecting, more calculations must be performed.
|
||||
let intersection = Intersect.INSIDE;
|
||||
if (!this.unionClippingRegions && length > 0) {
|
||||
intersection = Intersect.OUTSIDE;
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const plane = planes[i];
|
||||
|
||||
Plane.transform(plane, modelMatrix, scratchPlane); // ClippingPlane can be used for Plane math
|
||||
|
||||
const value = tileBoundingVolume.intersectPlane(scratchPlane);
|
||||
if (value === Intersect.INTERSECTING) {
|
||||
intersection = value;
|
||||
} else if (this._testIntersection(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return intersection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the owner for the input ClippingPlaneCollection if there wasn't another owner.
|
||||
* Destroys the owner's previous ClippingPlaneCollection if setting is successful.
|
||||
*
|
||||
* @param {ClippingPlaneCollection} [clippingPlaneCollection] A ClippingPlaneCollection (or undefined) being attached to an object
|
||||
* @param {object} owner An Object that should receive the new ClippingPlaneCollection
|
||||
* @param {string} key The Key for the Object to reference the ClippingPlaneCollection
|
||||
* @private
|
||||
*/
|
||||
ClippingPlaneCollection.setOwner = function (
|
||||
clippingPlaneCollection,
|
||||
owner,
|
||||
key,
|
||||
) {
|
||||
// Don't destroy the ClippingPlaneCollection if it is already owned by newOwner
|
||||
if (clippingPlaneCollection === owner[key]) {
|
||||
return;
|
||||
}
|
||||
// Destroy the existing ClippingPlaneCollection, if any
|
||||
owner[key] = owner[key] && owner[key].destroy();
|
||||
if (defined(clippingPlaneCollection)) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (defined(clippingPlaneCollection._owner)) {
|
||||
throw new DeveloperError(
|
||||
"ClippingPlaneCollection should only be assigned to one object",
|
||||
);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
clippingPlaneCollection._owner = owner;
|
||||
owner[key] = clippingPlaneCollection;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for checking if the context will allow clipping planes with floating point textures.
|
||||
*
|
||||
* @param {Context} context The Context that will contain clipped objects and clipping textures.
|
||||
* @returns {boolean} <code>true</code> if floating point textures can be used for clipping planes.
|
||||
* @private
|
||||
*/
|
||||
ClippingPlaneCollection.useFloatTexture = function (context) {
|
||||
return context.floatingPointTexture;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for getting the clipping plane collection's texture resolution.
|
||||
* If the ClippingPlaneCollection hasn't been updated, returns the resolution that will be
|
||||
* allocated based on the current plane count.
|
||||
*
|
||||
* @param {ClippingPlaneCollection} clippingPlaneCollection The clipping plane collection
|
||||
* @param {Context} context The rendering context
|
||||
* @param {Cartesian2} result A Cartesian2 for the result.
|
||||
* @returns {Cartesian2} The required resolution.
|
||||
* @private
|
||||
*/
|
||||
ClippingPlaneCollection.getTextureResolution = function (
|
||||
clippingPlaneCollection,
|
||||
context,
|
||||
result,
|
||||
) {
|
||||
const texture = clippingPlaneCollection.texture;
|
||||
if (defined(texture)) {
|
||||
result.x = texture.width;
|
||||
result.y = texture.height;
|
||||
return result;
|
||||
}
|
||||
|
||||
const pixelsNeeded = ClippingPlaneCollection.useFloatTexture(context)
|
||||
? clippingPlaneCollection.length
|
||||
: clippingPlaneCollection.length * 2;
|
||||
const requiredResolution = computeTextureResolution(pixelsNeeded, result);
|
||||
|
||||
// Allocate twice as much space as needed to avoid frequent texture reallocation.
|
||||
requiredResolution.y *= 2;
|
||||
return requiredResolution;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 ClippingPlaneCollection#destroy
|
||||
*/
|
||||
ClippingPlaneCollection.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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* clippingPlanes = clippingPlanes && clippingPlanes.destroy();
|
||||
*
|
||||
* @see ClippingPlaneCollection#isDestroyed
|
||||
*/
|
||||
ClippingPlaneCollection.prototype.destroy = function () {
|
||||
this._clippingPlanesTexture =
|
||||
this._clippingPlanesTexture && this._clippingPlanesTexture.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default ClippingPlaneCollection;
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Cartographic from "../Core/Cartographic.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import PolygonGeometry from "../Core/PolygonGeometry.js";
|
||||
import Rectangle from "../Core/Rectangle.js";
|
||||
|
||||
/**
|
||||
* A geodesic polygon to be used with {@link ClippingPlaneCollection} for selectively hiding regions in a model, a 3D tileset, or the globe.
|
||||
* @alias ClippingPolygon
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {Cartesian3[]} options.positions A list of three or more Cartesian coordinates defining the outer ring of the clipping polygon.
|
||||
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.default]
|
||||
*
|
||||
* @example
|
||||
* const positions = Cesium.Cartesian3.fromRadiansArray([
|
||||
* -1.3194369277314022,
|
||||
* 0.6988062530900625,
|
||||
* -1.31941,
|
||||
* 0.69879,
|
||||
* -1.3193955980204217,
|
||||
* 0.6988091578771254,
|
||||
* -1.3193931220959367,
|
||||
* 0.698743632490865,
|
||||
* -1.3194358224045408,
|
||||
* 0.6987471965556998,
|
||||
* ]);
|
||||
*
|
||||
* const polygon = new Cesium.ClippingPolygon({
|
||||
* positions: positions
|
||||
* });
|
||||
*/
|
||||
function ClippingPolygon(options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options", options);
|
||||
Check.typeOf.object("options.positions", options.positions);
|
||||
Check.typeOf.number.greaterThanOrEquals(
|
||||
"options.positions.length",
|
||||
options.positions.length,
|
||||
3,
|
||||
);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._ellipsoid = options.ellipsoid ?? Ellipsoid.default;
|
||||
this._positions = copyArrayCartesian3(options.positions);
|
||||
|
||||
/**
|
||||
* A copy of the input positions.
|
||||
*
|
||||
* This is used to detect modifications of the positions in
|
||||
* <code>coputeRectangle</code>: The rectangle only has
|
||||
* to be re-computed when these positions have changed.
|
||||
*
|
||||
* @type {Cartesian3[]|undefined}
|
||||
* @private
|
||||
*/
|
||||
this._cachedPositions = undefined;
|
||||
|
||||
/**
|
||||
* A cached version of the rectangle that is computed in
|
||||
* <code>computeRectangle</code>.
|
||||
*
|
||||
* This is only re-computed when the positions have changed, as
|
||||
* determined by comparing the <code>_positions</code> to the
|
||||
* <code>_cachedPositions</code>
|
||||
*
|
||||
* @type {Rectangle|undefined}
|
||||
* @private
|
||||
*/
|
||||
this._cachedRectangle = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy of the given array.
|
||||
*
|
||||
* If the input is undefined, then <code>undefined</code> is returned.
|
||||
*
|
||||
* Otherwise, the result will be a copy of the given array, where
|
||||
* each element is copied with <code>Cartesian3.clone</code>.
|
||||
*
|
||||
* @param {Cartesian3[]|undefined} input The input array
|
||||
* @returns {Cartesian3[]|undefined} The copy
|
||||
*/
|
||||
function copyArrayCartesian3(input) {
|
||||
if (!defined(input)) {
|
||||
return undefined;
|
||||
}
|
||||
const n = input.length;
|
||||
const output = Array(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
output[i] = Cartesian3.clone(input[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given arrays are component-wise equal.
|
||||
*
|
||||
* When both arrays are undefined, then <code>true</code> is returned.
|
||||
* When only one array is defined, or they are both defined but have
|
||||
* different lengths, then <code>false</code> is returned.
|
||||
*
|
||||
* Otherwise, returns whether the corresponding elements of the arrays
|
||||
* are equal, as of <code>Cartesian3.equals</code>.
|
||||
*
|
||||
* @param {Cartesian3[]|undefined} a The first array
|
||||
* @param {Cartesian3[]|undefined} b The second array
|
||||
* @returns {boolean} Whether the arrays are equal
|
||||
*/
|
||||
function equalsArrayCartesian3(a, b) {
|
||||
if (!defined(a) && !defined(b)) {
|
||||
return true;
|
||||
}
|
||||
if (defined(a) !== defined(b)) {
|
||||
return false;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
const n = a.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const ca = a[i];
|
||||
const cb = b[i];
|
||||
if (!Cartesian3.equals(ca, cb)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Object.defineProperties(ClippingPolygon.prototype, {
|
||||
/**
|
||||
* Returns the total number of positions in the polygon, include any holes.
|
||||
*
|
||||
* @memberof ClippingPolygon.prototype
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
length: {
|
||||
get: function () {
|
||||
return this._positions.length;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Returns the outer ring of positions.
|
||||
*
|
||||
* @memberof ClippingPolygon.prototype
|
||||
* @type {Cartesian3[]}
|
||||
* @readonly
|
||||
*/
|
||||
positions: {
|
||||
get: function () {
|
||||
return this._positions;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Returns the ellipsoid used to project the polygon onto surfaces when clipping.
|
||||
*
|
||||
* @memberof ClippingPolygon.prototype
|
||||
* @type {Ellipsoid}
|
||||
* @readonly
|
||||
*/
|
||||
ellipsoid: {
|
||||
get: function () {
|
||||
return this._ellipsoid;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Clones the ClippingPolygon without setting its ownership.
|
||||
* @param {ClippingPolygon} polygon The ClippingPolygon to be cloned
|
||||
* @param {ClippingPolygon} [result] The object on which to store the cloned parameters.
|
||||
* @returns {ClippingPolygon} a clone of the input ClippingPolygon
|
||||
*/
|
||||
ClippingPolygon.clone = function (polygon, result) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("polygon", polygon);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(result)) {
|
||||
return new ClippingPolygon({
|
||||
positions: polygon.positions,
|
||||
ellipsoid: polygon.ellipsoid,
|
||||
});
|
||||
}
|
||||
|
||||
result._ellipsoid = polygon.ellipsoid;
|
||||
result._positions.length = 0;
|
||||
result._positions.push(...polygon.positions);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares the provided ClippingPolygons and returns
|
||||
* <code>true</code> if they are equal, <code>false</code> otherwise.
|
||||
*
|
||||
* @param {ClippingPolygon} left The first polygon.
|
||||
* @param {ClippingPolygon} right The second polygon.
|
||||
* @returns {boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
|
||||
*/
|
||||
ClippingPolygon.equals = function (left, right) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("left", left);
|
||||
Check.typeOf.object("right", right);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
return (
|
||||
left.ellipsoid.equals(right.ellipsoid) && left.positions === right.positions
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes a cartographic rectangle which encloses the polygon defined by the list of positions, including cases over the international date line and the poles.
|
||||
*
|
||||
* @param {Rectangle} [result] An object in which to store the result.
|
||||
* @returns {Rectangle} The result rectangle
|
||||
*/
|
||||
ClippingPolygon.prototype.computeRectangle = function (result) {
|
||||
if (equalsArrayCartesian3(this._positions, this._cachedPositions)) {
|
||||
return Rectangle.clone(this._cachedRectangle, result);
|
||||
}
|
||||
const rectangle = PolygonGeometry.computeRectangleFromPositions(
|
||||
this.positions,
|
||||
this.ellipsoid,
|
||||
undefined,
|
||||
result,
|
||||
);
|
||||
this._cachedPositions = copyArrayCartesian3(this._positions);
|
||||
this._cachedRectangle = Rectangle.clone(rectangle);
|
||||
return rectangle;
|
||||
};
|
||||
|
||||
const scratchRectangle = new Rectangle();
|
||||
const spherePointScratch = new Cartesian3();
|
||||
/**
|
||||
* Computes a rectangle with the spherical extents that encloses the polygon defined by the list of positions, including cases over the international date line and the poles.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Rectangle} [result] An object in which to store the result.
|
||||
* @returns {Rectangle} The result rectangle with spherical extents.
|
||||
*/
|
||||
ClippingPolygon.prototype.computeSphericalExtents = function (result) {
|
||||
if (!defined(result)) {
|
||||
result = new Rectangle();
|
||||
}
|
||||
|
||||
const rectangle = this.computeRectangle(scratchRectangle);
|
||||
|
||||
let spherePoint = Cartographic.toCartesian(
|
||||
Rectangle.southwest(rectangle),
|
||||
this.ellipsoid,
|
||||
spherePointScratch,
|
||||
);
|
||||
|
||||
// Project into plane with vertical for latitude
|
||||
let magXY = Math.sqrt(
|
||||
spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y,
|
||||
);
|
||||
|
||||
// Use fastApproximateAtan2 for alignment with shader
|
||||
let sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);
|
||||
let sphereLongitude = CesiumMath.fastApproximateAtan2(
|
||||
spherePoint.x,
|
||||
spherePoint.y,
|
||||
);
|
||||
|
||||
result.south = sphereLatitude;
|
||||
result.west = sphereLongitude;
|
||||
|
||||
spherePoint = Cartographic.toCartesian(
|
||||
Rectangle.northeast(rectangle),
|
||||
this.ellipsoid,
|
||||
spherePointScratch,
|
||||
);
|
||||
|
||||
// Project into plane with vertical for latitude
|
||||
magXY = Math.sqrt(
|
||||
spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y,
|
||||
);
|
||||
|
||||
// Use fastApproximateAtan2 for alignment with shader
|
||||
sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);
|
||||
sphereLongitude = CesiumMath.fastApproximateAtan2(
|
||||
spherePoint.x,
|
||||
spherePoint.y,
|
||||
);
|
||||
|
||||
result.north = sphereLatitude;
|
||||
result.east = sphereLongitude;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export default ClippingPolygon;
|
||||
+1134
File diff suppressed because it is too large
Load Diff
+1041
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Specifies the type of the cloud that is added to a {@link CloudCollection} in {@link CloudCollection#add}.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
|
||||
const CloudType = {
|
||||
/**
|
||||
* Cumulus cloud.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
CUMULUS: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that the provided cloud type is a valid {@link CloudType}
|
||||
*
|
||||
* @param {CloudType} cloudType The cloud type to validate.
|
||||
* @returns {boolean} <code>true</code> if the provided cloud type is a valid value; otherwise, <code>false</code>.
|
||||
*
|
||||
* @example
|
||||
* if (!Cesium.CloudType.validate(cloudType)) {
|
||||
* throw new Cesium.DeveloperError('cloudType must be a valid value.');
|
||||
* }
|
||||
*/
|
||||
|
||||
CloudType.validate = function (cloudType) {
|
||||
return cloudType === CloudType.CUMULUS;
|
||||
};
|
||||
|
||||
Object.freeze(CloudType);
|
||||
|
||||
export default CloudType;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
/**
|
||||
* Defines different modes for blending between a target color and a primitive's source color.
|
||||
*
|
||||
* HIGHLIGHT multiplies the source color by the target color
|
||||
* REPLACE replaces the source color with the target color
|
||||
* MIX blends the source color and target color together
|
||||
*
|
||||
* @enum {number}
|
||||
*
|
||||
* @see Model.colorBlendMode
|
||||
*/
|
||||
const ColorBlendMode = {
|
||||
HIGHLIGHT: 0,
|
||||
REPLACE: 1,
|
||||
MIX: 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ColorBlendMode.getColorBlend = function (colorBlendMode, colorBlendAmount) {
|
||||
if (colorBlendMode === ColorBlendMode.HIGHLIGHT) {
|
||||
return 0.0;
|
||||
} else if (colorBlendMode === ColorBlendMode.REPLACE) {
|
||||
return 1.0;
|
||||
} else if (colorBlendMode === ColorBlendMode.MIX) {
|
||||
// The value 0.0 is reserved for highlight, so clamp to just above 0.0.
|
||||
return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
|
||||
}
|
||||
};
|
||||
|
||||
Object.freeze(ColorBlendMode);
|
||||
|
||||
export default ColorBlendMode;
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import getMagic from "../Core/getMagic.js";
|
||||
import RuntimeError from "../Core/RuntimeError.js";
|
||||
|
||||
/**
|
||||
* Represents the contents of a
|
||||
* {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Composite|Composite}
|
||||
* tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification|3D Tiles} tileset.
|
||||
* <p>
|
||||
* Implements the {@link Cesium3DTileContent} interface.
|
||||
* </p>
|
||||
*
|
||||
* @implements Cesium3DTileContent
|
||||
* @private
|
||||
*/
|
||||
class Composite3DTileContent {
|
||||
constructor(tileset, tile, resource, contents) {
|
||||
this._tileset = tileset;
|
||||
this._tile = tile;
|
||||
this._resource = resource;
|
||||
|
||||
if (!defined(contents)) {
|
||||
contents = [];
|
||||
}
|
||||
this._contents = contents;
|
||||
|
||||
this._metadata = undefined;
|
||||
this._group = undefined;
|
||||
this._ready = false;
|
||||
}
|
||||
|
||||
get featurePropertiesDirty() {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (contents[i].featurePropertiesDirty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set featurePropertiesDirty(value) {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].featurePropertiesDirty = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>featuresLength</code> for a tile in the composite.
|
||||
*/
|
||||
get featuresLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>pointsLength</code> for a tile in the composite.
|
||||
*/
|
||||
get pointsLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>trianglesLength</code> for a tile in the composite.
|
||||
*/
|
||||
get trianglesLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>geometryByteLength</code> for a tile in the composite.
|
||||
*/
|
||||
get geometryByteLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>texturesByteLength</code> for a tile in the composite.
|
||||
*/
|
||||
get texturesByteLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>0</code>. Instead call <code>batchTableByteLength</code> for a tile in the composite.
|
||||
*/
|
||||
get batchTableByteLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
get innerContents() {
|
||||
return this._contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the tile's content is ready to render; otherwise false
|
||||
*
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get ready() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
get tileset() {
|
||||
return this._tileset;
|
||||
}
|
||||
|
||||
get tile() {
|
||||
return this._tile;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._resource.getUrlComponent(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* both stores the content metadata and propagates the content metadata to all of its children.
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
get metadata() {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
set metadata(value) {
|
||||
this._metadata = value;
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].metadata = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>undefined</code>. Instead call <code>batchTable</code> for a tile in the composite.
|
||||
*/
|
||||
get batchTable() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* both stores the group metadata and propagates the group metadata to all of its children.
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
get group() {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
set group(value) {
|
||||
this._group = value;
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].group = value;
|
||||
}
|
||||
}
|
||||
|
||||
static async fromTileType(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
arrayBuffer,
|
||||
byteOffset,
|
||||
factory,
|
||||
) {
|
||||
byteOffset = byteOffset ?? 0;
|
||||
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const view = new DataView(arrayBuffer);
|
||||
byteOffset += sizeOfUint32; // Skip magic
|
||||
|
||||
const version = view.getUint32(byteOffset, true);
|
||||
if (version !== 1) {
|
||||
throw new RuntimeError(
|
||||
`Only Composite Tile version 1 is supported. Version ${version} is not.`,
|
||||
);
|
||||
}
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
// Skip byteLength
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
const tilesLength = view.getUint32(byteOffset, true);
|
||||
byteOffset += sizeOfUint32;
|
||||
|
||||
// For caching purposes, models within the composite tile must be
|
||||
// distinguished. To do this, add a query parameter ?compositeIndex=i.
|
||||
// Since composite tiles may contain other composite tiles, check for an
|
||||
// existing prefix and separate them with underscores. e.g.
|
||||
// ?compositeIndex=0_1_1
|
||||
let prefix = resource.queryParameters.compositeIndex;
|
||||
if (defined(prefix)) {
|
||||
// We'll be adding another value at the end, so add an underscore.
|
||||
prefix = `${prefix}_`;
|
||||
} else {
|
||||
// no prefix
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
promises.length = tilesLength;
|
||||
for (let i = 0; i < tilesLength; ++i) {
|
||||
const tileType = getMagic(uint8Array, byteOffset);
|
||||
|
||||
// Tile byte length is stored after magic and version
|
||||
const tileByteLength = view.getUint32(
|
||||
byteOffset + sizeOfUint32 * 2,
|
||||
true,
|
||||
);
|
||||
|
||||
const contentFactory = factory[tileType];
|
||||
|
||||
// Label which content within the composite this is
|
||||
const compositeIndex = `${prefix}${i}`;
|
||||
const childResource = resource.getDerivedResource({
|
||||
queryParameters: {
|
||||
compositeIndex: compositeIndex,
|
||||
},
|
||||
});
|
||||
|
||||
if (defined(contentFactory)) {
|
||||
promises[i] = Promise.resolve(
|
||||
contentFactory(tileset, tile, childResource, arrayBuffer, byteOffset),
|
||||
);
|
||||
} else {
|
||||
throw new RuntimeError(
|
||||
`Unknown tile content type, ${tileType}, inside Composite tile`,
|
||||
);
|
||||
}
|
||||
|
||||
byteOffset += tileByteLength;
|
||||
}
|
||||
|
||||
const innerContents = await Promise.all(promises);
|
||||
const content = new Composite3DTileContent(
|
||||
tileset,
|
||||
tile,
|
||||
resource,
|
||||
innerContents,
|
||||
);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>false</code>. Instead call <code>hasProperty</code> for a tile in the composite.
|
||||
*/
|
||||
hasProperty(batchId, name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the {@link Cesium3DTileContent} interface. <code>Composite3DTileContent</code>
|
||||
* always returns <code>undefined</code>. Instead call <code>getFeature</code> for a tile in the composite.
|
||||
*/
|
||||
getFeature(batchId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
applyDebugSettings(enabled, color) {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].applyDebugSettings(enabled, color);
|
||||
}
|
||||
}
|
||||
|
||||
applyStyle(style) {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].applyStyle(style);
|
||||
}
|
||||
}
|
||||
|
||||
update(tileset, frameState) {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
let ready = true;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].update(tileset, frameState);
|
||||
ready = ready && contents[i].ready;
|
||||
}
|
||||
|
||||
if (!this._ready && ready) {
|
||||
this._ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates.
|
||||
*
|
||||
* @param {Ray} ray The ray to test for intersection.
|
||||
* @param {FrameState} frameState The frame state.
|
||||
* @param {Cartesian3|undefined} [result] The intersection or <code>undefined</code> if none was found.
|
||||
* @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
pick(ray, frameState, result) {
|
||||
if (!this._ready) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let intersection;
|
||||
let minDistance = Number.POSITIVE_INFINITY;
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const candidate = contents[i].pick(ray, frameState, result);
|
||||
|
||||
if (!defined(candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = Cartesian3.distance(ray.origin, candidate);
|
||||
if (distance < minDistance) {
|
||||
intersection = candidate;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined(intersection)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
isDestroyed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
const contents = this._contents;
|
||||
const length = contents.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
contents[i].destroy();
|
||||
}
|
||||
return destroyObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
|
||||
|
||||
export default Composite3DTileContent;
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
import addAllToArray from "../Core/addAllToArray.js";
|
||||
import clone from "../Core/clone.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import Expression from "./Expression.js";
|
||||
|
||||
/**
|
||||
* An expression for a style applied to a {@link Cesium3DTileset}.
|
||||
* <p>
|
||||
* Evaluates a conditions expression defined using the
|
||||
* {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling|3D Tiles Styling language}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Implements the {@link StyleExpression} interface.
|
||||
* </p>
|
||||
*
|
||||
* @alias ConditionsExpression
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} [conditionsExpression] The conditions expression defined using the 3D Tiles Styling language.
|
||||
* @param {object} [defines] Defines in the style.
|
||||
*
|
||||
* @example
|
||||
* const expression = new Cesium.ConditionsExpression({
|
||||
* conditions : [
|
||||
* ['${Area} > 10, 'color("#FF0000")'],
|
||||
* ['${id} !== "1"', 'color("#00FF00")'],
|
||||
* ['true', 'color("#FFFFFF")']
|
||||
* ]
|
||||
* });
|
||||
* expression.evaluateColor(feature, result); // returns a Cesium.Color object
|
||||
*/
|
||||
function ConditionsExpression(conditionsExpression, defines) {
|
||||
this._conditionsExpression = clone(conditionsExpression, true);
|
||||
this._conditions = conditionsExpression.conditions;
|
||||
this._runtimeConditions = undefined;
|
||||
|
||||
setRuntime(this, defines);
|
||||
}
|
||||
|
||||
Object.defineProperties(ConditionsExpression.prototype, {
|
||||
/**
|
||||
* Gets the conditions expression defined in the 3D Tiles Styling language.
|
||||
*
|
||||
* @memberof ConditionsExpression.prototype
|
||||
*
|
||||
* @type {object}
|
||||
* @readonly
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
conditionsExpression: {
|
||||
get: function () {
|
||||
return this._conditionsExpression;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function Statement(condition, expression) {
|
||||
this.condition = condition;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
function setRuntime(expression, defines) {
|
||||
const runtimeConditions = [];
|
||||
const conditions = expression._conditions;
|
||||
if (!defined(conditions)) {
|
||||
return;
|
||||
}
|
||||
const length = conditions.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const statement = conditions[i];
|
||||
const cond = String(statement[0]);
|
||||
const condExpression = String(statement[1]);
|
||||
runtimeConditions.push(
|
||||
new Statement(
|
||||
new Expression(cond, defines),
|
||||
new Expression(condExpression, defines),
|
||||
),
|
||||
);
|
||||
}
|
||||
expression._runtimeConditions = runtimeConditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the result of an expression, optionally using the provided feature's properties. If the result of
|
||||
* the expression in the
|
||||
* {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling|3D Tiles Styling language}
|
||||
* is of type <code>Boolean</code>, <code>Number</code>, or <code>String</code>, the corresponding JavaScript
|
||||
* primitive type will be returned. If the result is a <code>RegExp</code>, a Javascript <code>RegExp</code>
|
||||
* object will be returned. If the result is a <code>Cartesian2</code>, <code>Cartesian3</code>, or <code>Cartesian4</code>,
|
||||
* a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} object will be returned. If the <code>result</code> argument is
|
||||
* a {@link Color}, the {@link Cartesian4} value is converted to a {@link Color} and then returned.
|
||||
*
|
||||
* @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression.
|
||||
* @param {object} [result] The object onto which to store the result.
|
||||
* @returns {boolean|number|string|RegExp|Cartesian2|Cartesian3|Cartesian4|Color} The result of evaluating the expression.
|
||||
*/
|
||||
ConditionsExpression.prototype.evaluate = function (feature, result) {
|
||||
const conditions = this._runtimeConditions;
|
||||
if (!defined(conditions)) {
|
||||
return undefined;
|
||||
}
|
||||
const length = conditions.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const statement = conditions[i];
|
||||
if (statement.condition.evaluate(feature)) {
|
||||
return statement.expression.evaluate(feature, result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluates the result of a Color expression, using the values defined by a feature.
|
||||
* <p>
|
||||
* This is equivalent to {@link ConditionsExpression#evaluate} but always returns a {@link Color} object.
|
||||
* </p>
|
||||
* @param {Cesium3DTileFeature} feature The feature whose properties may be used as variables in the expression.
|
||||
* @param {Color} [result] The object in which to store the result
|
||||
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
|
||||
*/
|
||||
ConditionsExpression.prototype.evaluateColor = function (feature, result) {
|
||||
const conditions = this._runtimeConditions;
|
||||
if (!defined(conditions)) {
|
||||
return undefined;
|
||||
}
|
||||
const length = conditions.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const statement = conditions[i];
|
||||
if (statement.condition.evaluate(feature)) {
|
||||
return statement.expression.evaluateColor(feature, result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the shader function for this expression.
|
||||
* Returns undefined if the shader function can't be generated from this expression.
|
||||
*
|
||||
* @param {string} functionSignature Signature of the generated function.
|
||||
* @param {object} variableSubstitutionMap Maps variable names to shader variable names.
|
||||
* @param {object} shaderState Stores information about the generated shader function, including whether it is translucent.
|
||||
* @param {string} returnType The return type of the generated function.
|
||||
*
|
||||
* @returns {string} The shader function.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ConditionsExpression.prototype.getShaderFunction = function (
|
||||
functionSignature,
|
||||
variableSubstitutionMap,
|
||||
shaderState,
|
||||
returnType,
|
||||
) {
|
||||
const conditions = this._runtimeConditions;
|
||||
if (!defined(conditions) || conditions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let shaderFunction = "";
|
||||
const length = conditions.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const statement = conditions[i];
|
||||
|
||||
const condition = statement.condition.getShaderExpression(
|
||||
variableSubstitutionMap,
|
||||
shaderState,
|
||||
);
|
||||
const expression = statement.expression.getShaderExpression(
|
||||
variableSubstitutionMap,
|
||||
shaderState,
|
||||
);
|
||||
|
||||
// Build the if/else chain from the list of conditions
|
||||
shaderFunction +=
|
||||
` ${i === 0 ? "if" : "else if"} (${condition})\n` +
|
||||
` {\n` +
|
||||
` return ${expression};\n` +
|
||||
` }\n`;
|
||||
}
|
||||
|
||||
shaderFunction =
|
||||
`${returnType} ${functionSignature}\n` +
|
||||
`{\n${shaderFunction} return ${returnType}(1.0);\n` + // Return a default value if no conditions are met
|
||||
`}\n`;
|
||||
|
||||
return shaderFunction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the variables used by the expression.
|
||||
*
|
||||
* @returns {string[]} The variables used by the expression.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ConditionsExpression.prototype.getVariables = function () {
|
||||
let variables = [];
|
||||
|
||||
const conditions = this._runtimeConditions;
|
||||
if (!defined(conditions) || conditions.length === 0) {
|
||||
return variables;
|
||||
}
|
||||
|
||||
const length = conditions.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const statement = conditions[i];
|
||||
addAllToArray(variables, statement.condition.getVariables());
|
||||
addAllToArray(variables, statement.expression.getVariables());
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
variables = variables.filter(function (variable, index, variables) {
|
||||
return variables.indexOf(variable) === index;
|
||||
});
|
||||
|
||||
return variables;
|
||||
};
|
||||
|
||||
export default ConditionsExpression;
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
|
||||
const defaultAngle = CesiumMath.toRadians(30.0);
|
||||
|
||||
/**
|
||||
* A ParticleEmitter that emits particles within a cone.
|
||||
* Particles will be positioned at the tip of the cone and have initial velocities going towards the base.
|
||||
*
|
||||
* @alias ConeEmitter
|
||||
* @constructor
|
||||
*
|
||||
* @param {number} [angle=Cesium.Math.toRadians(30.0)] The angle of the cone in radians.
|
||||
*/
|
||||
function ConeEmitter(angle) {
|
||||
this._angle = angle ?? defaultAngle;
|
||||
}
|
||||
|
||||
Object.defineProperties(ConeEmitter.prototype, {
|
||||
/**
|
||||
* The angle of the cone in radians.
|
||||
* @memberof CircleEmitter.prototype
|
||||
* @type {number}
|
||||
* @default Cesium.Math.toRadians(30.0)
|
||||
*/
|
||||
angle: {
|
||||
get: function () {
|
||||
return this._angle;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
this._angle = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the given {Particle} by setting it's position and velocity.
|
||||
*
|
||||
* @private
|
||||
* @param {Particle} particle The particle to initialize
|
||||
*/
|
||||
ConeEmitter.prototype.emit = function (particle) {
|
||||
const radius = Math.tan(this._angle);
|
||||
|
||||
// Compute a random point on the cone's base
|
||||
const theta = CesiumMath.randomBetween(0.0, CesiumMath.TWO_PI);
|
||||
const rad = CesiumMath.randomBetween(0.0, radius);
|
||||
|
||||
const x = rad * Math.cos(theta);
|
||||
const y = rad * Math.sin(theta);
|
||||
const z = 1.0;
|
||||
|
||||
particle.velocity = Cartesian3.fromElements(x, y, z, particle.velocity);
|
||||
Cartesian3.normalize(particle.velocity, particle.velocity);
|
||||
particle.position = Cartesian3.clone(Cartesian3.ZERO, particle.position);
|
||||
};
|
||||
export default ConeEmitter;
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
import Check from "../Core/Check.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import MetadataEntity from "./MetadataEntity.js";
|
||||
|
||||
/**
|
||||
* Metadata about the content of a 3D Tile. This represents the content metadata JSON (3D Tiles 1.1)
|
||||
* or the <code>3DTILES_metadata</code> extension on a single {@link Cesium3DTileContent}
|
||||
* <p>
|
||||
* See the {@link https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_metadata|3DTILES_metadata Extension} for 3D Tiles
|
||||
* </p>
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {object} options.content Either the content metadata JSON (3D Tiles 1.1) or the extension JSON attached to the content.
|
||||
* @param {MetadataClass} options.class The class that the content metadata conforms to.
|
||||
*
|
||||
* @alias ContentMetadata
|
||||
* @constructor
|
||||
* @private
|
||||
* @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
|
||||
*/
|
||||
function ContentMetadata(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
const content = options.content;
|
||||
const metadataClass = options.class;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options.content", content);
|
||||
Check.typeOf.object("options.class", metadataClass);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._class = metadataClass;
|
||||
this._properties = content.properties;
|
||||
this._extensions = content.extensions;
|
||||
this._extras = content.extras;
|
||||
}
|
||||
|
||||
Object.defineProperties(ContentMetadata.prototype, {
|
||||
/**
|
||||
* The class that properties conform to.
|
||||
*
|
||||
* @memberof ContentMetadata.prototype
|
||||
* @type {MetadataClass}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
class: {
|
||||
get: function () {
|
||||
return this._class;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Extra user-defined properties.
|
||||
*
|
||||
* @memberof ContentMetadata.prototype
|
||||
* @type {object}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
extras: {
|
||||
get: function () {
|
||||
return this._extras;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* An object containing extensions.
|
||||
*
|
||||
* @memberof ContentMetadata.prototype
|
||||
* @type {object}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
extensions: {
|
||||
get: function () {
|
||||
return this._extensions;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns whether the content has this property.
|
||||
*
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @returns {boolean} Whether the content has this property.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.hasProperty = function (propertyId) {
|
||||
return MetadataEntity.hasProperty(propertyId, this._properties, this._class);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the content has a property with the given semantic.
|
||||
*
|
||||
* @param {string} semantic The case-sensitive semantic of the property.
|
||||
* @returns {boolean} Whether the content has a property with the given semantic.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.hasPropertyBySemantic = function (semantic) {
|
||||
return MetadataEntity.hasPropertyBySemantic(
|
||||
semantic,
|
||||
this._properties,
|
||||
this._class,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of property IDs.
|
||||
*
|
||||
* @param {string[]} [results] An array into which to store the results.
|
||||
* @returns {string[]} The property IDs.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.getPropertyIds = function (results) {
|
||||
return MetadataEntity.getPropertyIds(this._properties, this._class, results);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the property with the given ID.
|
||||
* <p>
|
||||
* If the property is normalized the normalized value is returned.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the content does not have this property.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.getProperty = function (propertyId) {
|
||||
return MetadataEntity.getProperty(propertyId, this._properties, this._class);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the value of the property with the given ID.
|
||||
* <p>
|
||||
* If the property is normalized a normalized value must be provided to this function.
|
||||
* </p>
|
||||
*
|
||||
* @param {string} propertyId The case-sensitive ID of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
* @returns {boolean} <code>true</code> if the property was set, <code>false</code> otherwise.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.setProperty = function (propertyId, value) {
|
||||
return MetadataEntity.setProperty(
|
||||
propertyId,
|
||||
value,
|
||||
this._properties,
|
||||
this._class,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a copy of the value of the property with the given semantic.
|
||||
*
|
||||
* @param {string} semantic The case-sensitive semantic of the property.
|
||||
* @returns {*} The value of the property or <code>undefined</code> if the content does not have this semantic.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.getPropertyBySemantic = function (semantic) {
|
||||
return MetadataEntity.getPropertyBySemantic(
|
||||
semantic,
|
||||
this._properties,
|
||||
this._class,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the value of the property with the given semantic.
|
||||
*
|
||||
* @param {string} semantic The case-sensitive semantic of the property.
|
||||
* @param {*} value The value of the property that will be copied.
|
||||
* @returns {boolean} <code>true</code> if the property was set, <code>false</code> otherwise.
|
||||
* @private
|
||||
*/
|
||||
ContentMetadata.prototype.setPropertyBySemantic = function (semantic, value) {
|
||||
return MetadataEntity.setPropertyBySemantic(
|
||||
semantic,
|
||||
value,
|
||||
this._properties,
|
||||
this._class,
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentMetadata;
|
||||
+654
@@ -0,0 +1,654 @@
|
||||
import AssociativeArray from "../Core/AssociativeArray.js";
|
||||
import buildModuleUrl from "../Core/buildModuleUrl.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import Uri from "urijs";
|
||||
|
||||
const mobileWidth = 576;
|
||||
const lightboxHeight = 100;
|
||||
const textColor = "#ffffff";
|
||||
const highlightColor = "#48b";
|
||||
|
||||
/**
|
||||
* Used to sort the credits by frequency of appearance
|
||||
* when they are later displayed.
|
||||
*
|
||||
* @alias CreditDisplay.CreditDisplayElement
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function CreditDisplayElement(credit, count) {
|
||||
this.credit = credit;
|
||||
this.count = count ?? 1;
|
||||
}
|
||||
|
||||
function contains(credits, credit) {
|
||||
const len = credits.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const existingCredit = credits[i];
|
||||
if (Credit.equals(existingCredit, credit)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function swapCesiumCredit(creditDisplay) {
|
||||
// We don't want to clutter the screen with the Cesium logo and the Cesium ion
|
||||
// logo at the same time. Since the ion logo is required, we just replace the
|
||||
// Cesium logo or add the logo if the Cesium one was removed.
|
||||
const previousCredit = creditDisplay._previousCesiumCredit;
|
||||
const currentCredit = creditDisplay._currentCesiumCredit;
|
||||
if (Credit.equals(currentCredit, previousCredit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined(previousCredit)) {
|
||||
creditDisplay._cesiumCreditContainer.removeChild(previousCredit.element);
|
||||
}
|
||||
if (defined(currentCredit)) {
|
||||
creditDisplay._cesiumCreditContainer.appendChild(currentCredit.element);
|
||||
}
|
||||
|
||||
creditDisplay._previousCesiumCredit = currentCredit;
|
||||
}
|
||||
|
||||
const delimiterClassName = "cesium-credit-delimiter";
|
||||
|
||||
function createDelimiterElement(delimiter) {
|
||||
const delimiterElement = document.createElement("span");
|
||||
delimiterElement.textContent = delimiter;
|
||||
delimiterElement.className = delimiterClassName;
|
||||
return delimiterElement;
|
||||
}
|
||||
|
||||
function createCreditElement(element, elementWrapperTagName) {
|
||||
// may need to wrap the credit in another element
|
||||
if (defined(elementWrapperTagName)) {
|
||||
const wrapper = document.createElement(elementWrapperTagName);
|
||||
wrapper._creditId = element._creditId;
|
||||
wrapper.appendChild(element);
|
||||
element = wrapper;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function displayCredits(container, credits, delimiter, elementWrapperTagName) {
|
||||
const childNodes = container.childNodes;
|
||||
let domIndex = -1;
|
||||
|
||||
// Sort the credits such that more frequent credits appear first
|
||||
credits.sort(function (credit1, credit2) {
|
||||
return credit2.count - credit1.count;
|
||||
});
|
||||
|
||||
for (let creditIndex = 0; creditIndex < credits.length; ++creditIndex) {
|
||||
const credit = credits[creditIndex].credit;
|
||||
if (defined(credit)) {
|
||||
domIndex = creditIndex;
|
||||
if (defined(delimiter)) {
|
||||
// credits may be separated by delimiters
|
||||
domIndex *= 2;
|
||||
if (creditIndex > 0) {
|
||||
const delimiterDomIndex = domIndex - 1;
|
||||
if (childNodes.length <= delimiterDomIndex) {
|
||||
container.appendChild(createDelimiterElement(delimiter));
|
||||
} else {
|
||||
const existingDelimiter = childNodes[delimiterDomIndex];
|
||||
if (existingDelimiter.className !== delimiterClassName) {
|
||||
container.replaceChild(
|
||||
createDelimiterElement(delimiter),
|
||||
existingDelimiter,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const element = credit.element;
|
||||
|
||||
// check to see if the correct credit is in the right place
|
||||
if (childNodes.length <= domIndex) {
|
||||
container.appendChild(
|
||||
createCreditElement(element, elementWrapperTagName),
|
||||
);
|
||||
} else {
|
||||
const existingElement = childNodes[domIndex];
|
||||
if (existingElement._creditId !== credit._id) {
|
||||
// not the right credit, swap it in
|
||||
container.replaceChild(
|
||||
createCreditElement(element, elementWrapperTagName),
|
||||
existingElement,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// any remaining nodes in the container are unnecessary
|
||||
++domIndex;
|
||||
while (domIndex < childNodes.length) {
|
||||
container.removeChild(childNodes[domIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
function styleLightboxContainer(that) {
|
||||
const lightboxCredits = that._lightboxCredits;
|
||||
const width = that.viewport.clientWidth;
|
||||
const height = that.viewport.clientHeight;
|
||||
if (width !== that._lastViewportWidth) {
|
||||
if (width < mobileWidth) {
|
||||
lightboxCredits.className =
|
||||
"cesium-credit-lightbox cesium-credit-lightbox-mobile";
|
||||
lightboxCredits.style.marginTop = "0";
|
||||
} else {
|
||||
lightboxCredits.className =
|
||||
"cesium-credit-lightbox cesium-credit-lightbox-expanded";
|
||||
lightboxCredits.style.marginTop = `${Math.floor(
|
||||
(height - lightboxCredits.clientHeight) * 0.5,
|
||||
)}px`;
|
||||
}
|
||||
that._lastViewportWidth = width;
|
||||
}
|
||||
|
||||
if (width >= mobileWidth && height !== that._lastViewportHeight) {
|
||||
lightboxCredits.style.marginTop = `${Math.floor(
|
||||
(height - lightboxCredits.clientHeight) * 0.5,
|
||||
)}px`;
|
||||
that._lastViewportHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
function appendCss(container) {
|
||||
const style = /*css*/ `
|
||||
.cesium-credit-lightbox-overlay {
|
||||
display: none;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(80, 80, 80, 0.8);
|
||||
}
|
||||
|
||||
.cesium-credit-lightbox {
|
||||
background-color: #303336;
|
||||
color: ${textColor};
|
||||
position: relative;
|
||||
min-height: ${lightboxHeight}px;
|
||||
margin: auto;
|
||||
}
|
||||
.cesium-credit-lightbox > ul > li a,
|
||||
.cesium-credit-lightbox > ul > li a:visited,
|
||||
.cesium-credit-wrapper a,
|
||||
.cesium-credit-wrapper a:visited {
|
||||
color: ${textColor};
|
||||
}
|
||||
.cesium-credit-lightbox > ul > li a:hover {
|
||||
color: ${highlightColor};
|
||||
}
|
||||
.cesium-credit-lightbox.cesium-credit-lightbox-expanded {
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
max-width: 470px;
|
||||
}
|
||||
.cesium-credit-lightbox.cesium-credit-lightbox-mobile {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.cesium-credit-lightbox-title {
|
||||
padding: 20px 20px 0 20px;
|
||||
}
|
||||
.cesium-credit-lightbox-close {
|
||||
font-size: 18pt;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 6px;
|
||||
color: ${textColor};
|
||||
}
|
||||
.cesium-credit-lightbox-close:hover {
|
||||
color: ${highlightColor};
|
||||
}
|
||||
.cesium-credit-lightbox > ul {
|
||||
margin: 0;
|
||||
padding: 12px 20px 12px 40px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cesium-credit-lightbox > ul > li {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.cesium-credit-lightbox > ul > li * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cesium-credit-expand-link {
|
||||
padding-left: 5px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: ${textColor};
|
||||
}
|
||||
.cesium-credit-expand-link:hover {
|
||||
color: ${highlightColor};
|
||||
}
|
||||
|
||||
.cesium-credit-text {
|
||||
color: ${textColor};
|
||||
}
|
||||
|
||||
.cesium-credit-delimiter {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.cesium-credit-textContainer *,
|
||||
.cesium-credit-logoContainer * {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.cesium-credit-textContainer a:hover {
|
||||
color: ${highlightColor}
|
||||
}
|
||||
|
||||
.cesium-credit-textContainer .cesium-credit-wrapper:first-of-type {
|
||||
padding-left: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
function getShadowRoot(container) {
|
||||
if (container.shadowRoot) {
|
||||
return container.shadowRoot;
|
||||
}
|
||||
if (container.getRootNode) {
|
||||
const root = container.getRootNode();
|
||||
if (root instanceof ShadowRoot) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const shadowRootOrDocumentHead = getShadowRoot(container) ?? document.head;
|
||||
const styleElem = document.createElement("style");
|
||||
styleElem.innerHTML = style;
|
||||
shadowRootOrDocumentHead.appendChild(styleElem);
|
||||
}
|
||||
|
||||
/**
|
||||
* The credit display is responsible for displaying credits on screen.
|
||||
*
|
||||
* @param {HTMLElement} container The HTML element where credits will be displayed
|
||||
* @param {string} [delimiter= '•'] The string to separate text credits
|
||||
* @param {HTMLElement} [viewport=document.body] The HTML element that will contain the credits popup
|
||||
*
|
||||
* @alias CreditDisplay
|
||||
* @constructor
|
||||
*
|
||||
* @example
|
||||
* // Add a credit with a tooltip, image and link to display onscreen
|
||||
* const credit = new Cesium.Credit(`<a href="https://cesium.com/" target="_blank"><img src="/images/cesium_logo.png" title="Cesium"/></a>`, true);
|
||||
* viewer.creditDisplay.addStaticCredit(credit);
|
||||
*
|
||||
* @example
|
||||
* // Add a credit with a plaintext link to display in the lightbox
|
||||
* const credit = new Cesium.Credit('<a href="https://cesium.com/" target="_blank">Cesium</a>');
|
||||
* viewer.creditDisplay.addStaticCredit(credit);
|
||||
*/
|
||||
function CreditDisplay(container, delimiter, viewport) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("container", container);
|
||||
//>>includeEnd('debug');
|
||||
const that = this;
|
||||
|
||||
viewport = viewport ?? document.body;
|
||||
|
||||
const lightbox = document.createElement("div");
|
||||
lightbox.className = "cesium-credit-lightbox-overlay";
|
||||
viewport.appendChild(lightbox);
|
||||
|
||||
const lightboxCredits = document.createElement("div");
|
||||
lightboxCredits.className = "cesium-credit-lightbox";
|
||||
lightbox.appendChild(lightboxCredits);
|
||||
|
||||
function hideLightbox(event) {
|
||||
if (lightboxCredits.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
that.hideLightbox();
|
||||
}
|
||||
lightbox.addEventListener("click", hideLightbox, false);
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "cesium-credit-lightbox-title";
|
||||
title.textContent = "Data provided by:";
|
||||
lightboxCredits.appendChild(title);
|
||||
|
||||
const closeButton = document.createElement("a");
|
||||
closeButton.onclick = this.hideLightbox.bind(this);
|
||||
closeButton.innerHTML = "×";
|
||||
closeButton.className = "cesium-credit-lightbox-close";
|
||||
lightboxCredits.appendChild(closeButton);
|
||||
|
||||
const creditList = document.createElement("ul");
|
||||
lightboxCredits.appendChild(creditList);
|
||||
|
||||
const cesiumCreditContainer = document.createElement("div");
|
||||
cesiumCreditContainer.className = "cesium-credit-logoContainer";
|
||||
cesiumCreditContainer.style.display = "inline";
|
||||
container.appendChild(cesiumCreditContainer);
|
||||
|
||||
const screenContainer = document.createElement("div");
|
||||
screenContainer.className = "cesium-credit-textContainer";
|
||||
screenContainer.style.display = "inline";
|
||||
container.appendChild(screenContainer);
|
||||
|
||||
const expandLink = document.createElement("a");
|
||||
expandLink.className = "cesium-credit-expand-link";
|
||||
expandLink.onclick = this.showLightbox.bind(this);
|
||||
expandLink.textContent = "Data attribution";
|
||||
container.appendChild(expandLink);
|
||||
|
||||
appendCss(container);
|
||||
const cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit);
|
||||
|
||||
this._delimiter = delimiter ?? "•";
|
||||
this._screenContainer = screenContainer;
|
||||
this._cesiumCreditContainer = cesiumCreditContainer;
|
||||
this._lastViewportHeight = undefined;
|
||||
this._lastViewportWidth = undefined;
|
||||
this._lightboxCredits = lightboxCredits;
|
||||
this._creditList = creditList;
|
||||
this._lightbox = lightbox;
|
||||
this._hideLightbox = hideLightbox;
|
||||
this._expandLink = expandLink;
|
||||
this._expanded = false;
|
||||
this._staticCredits = [];
|
||||
this._cesiumCredit = cesiumCredit;
|
||||
this._previousCesiumCredit = undefined;
|
||||
this._currentCesiumCredit = cesiumCredit;
|
||||
this._creditDisplayElementPool = [];
|
||||
this._creditDisplayElementIndex = 0;
|
||||
|
||||
this._currentFrameCredits = {
|
||||
screenCredits: new AssociativeArray(),
|
||||
lightboxCredits: new AssociativeArray(),
|
||||
};
|
||||
|
||||
this._defaultCredit = undefined;
|
||||
|
||||
this.viewport = viewport;
|
||||
|
||||
/**
|
||||
* The HTML element where credits will be displayed.
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
function setCredit(creditDisplay, credits, credit, count) {
|
||||
count = count ?? 1;
|
||||
let creditDisplayElement = credits.get(credit.id);
|
||||
if (!defined(creditDisplayElement)) {
|
||||
const pool = creditDisplay._creditDisplayElementPool;
|
||||
const poolIndex = creditDisplay._creditDisplayElementPoolIndex;
|
||||
if (poolIndex < pool.length) {
|
||||
creditDisplayElement = pool[poolIndex];
|
||||
creditDisplayElement.credit = credit;
|
||||
creditDisplayElement.count = count;
|
||||
} else {
|
||||
creditDisplayElement = new CreditDisplayElement(credit, count);
|
||||
pool.push(creditDisplayElement);
|
||||
}
|
||||
++creditDisplay._creditDisplayElementPoolIndex;
|
||||
credits.set(credit.id, creditDisplayElement);
|
||||
} else if (creditDisplayElement.count < Number.MAX_VALUE) {
|
||||
creditDisplayElement.count += count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link Credit} that will show on screen or in the lightbox until
|
||||
* the next frame. This is mostly for internal use. Use {@link CreditDisplay.addStaticCredit} to add a persistent credit to the screen.
|
||||
*
|
||||
* @see CreditDisplay.addStaticCredit
|
||||
*
|
||||
* @param {Credit} credit The credit to display in the next frame.
|
||||
*/
|
||||
CreditDisplay.prototype.addCreditToNextFrame = function (credit) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("credit", credit);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (credit.isIon()) {
|
||||
// If this is the an ion logo credit from the ion server
|
||||
// Just use the default credit (which is identical) to avoid blinking
|
||||
if (!defined(this._defaultCredit)) {
|
||||
this._defaultCredit = Credit.clone(getDefaultCredit());
|
||||
}
|
||||
this._currentCesiumCredit = this._defaultCredit;
|
||||
return;
|
||||
}
|
||||
|
||||
let credits;
|
||||
if (!credit.showOnScreen) {
|
||||
credits = this._currentFrameCredits.lightboxCredits;
|
||||
} else {
|
||||
credits = this._currentFrameCredits.screenCredits;
|
||||
}
|
||||
|
||||
setCredit(this, credits, credit);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a {@link Credit} that will show on screen or in the lightbox until removed with {@link CreditDisplay.removeStaticCredit}.
|
||||
*
|
||||
* @param {Credit} credit The credit to added
|
||||
*
|
||||
* @example
|
||||
* // Add a credit with a tooltip, image and link to display onscreen
|
||||
* const credit = new Cesium.Credit(`<a href="https://cesium.com/" target="_blank"><img src="/images/cesium_logo.png" title="Cesium"/></a>`, true);
|
||||
* viewer.creditDisplay.addStaticCredit(credit);
|
||||
*
|
||||
* @example
|
||||
* // Add a credit with a plaintext link to display in the lightbox
|
||||
* const credit = new Cesium.Credit('<a href="https://cesium.com/" target="_blank">Cesium</a>');
|
||||
* viewer.creditDisplay.addStaticCredit(credit);
|
||||
*/
|
||||
CreditDisplay.prototype.addStaticCredit = function (credit) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("credit", credit);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const staticCredits = this._staticCredits;
|
||||
if (!contains(staticCredits, credit)) {
|
||||
staticCredits.push(credit);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a static credit shown on screen or in the lightbox.
|
||||
*
|
||||
* @param {Credit} credit The credit to be removed.
|
||||
*/
|
||||
CreditDisplay.prototype.removeStaticCredit = function (credit) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("credit", credit);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const staticCredits = this._staticCredits;
|
||||
const index = staticCredits.indexOf(credit);
|
||||
if (index !== -1) {
|
||||
staticCredits.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
CreditDisplay.prototype.showLightbox = function () {
|
||||
this._lightbox.style.display = "block";
|
||||
this._expanded = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
CreditDisplay.prototype.hideLightbox = function () {
|
||||
this._lightbox.style.display = "none";
|
||||
this._expanded = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the credit display before a new frame is rendered.
|
||||
*/
|
||||
CreditDisplay.prototype.update = function () {
|
||||
if (this._expanded) {
|
||||
styleLightboxContainer(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the credit display to a beginning of frame state, clearing out current credits.
|
||||
*/
|
||||
CreditDisplay.prototype.beginFrame = function () {
|
||||
const currentFrameCredits = this._currentFrameCredits;
|
||||
this._creditDisplayElementPoolIndex = 0;
|
||||
|
||||
const screenCredits = currentFrameCredits.screenCredits;
|
||||
const lightboxCredits = currentFrameCredits.lightboxCredits;
|
||||
|
||||
screenCredits.removeAll();
|
||||
lightboxCredits.removeAll();
|
||||
|
||||
const staticCredits = this._staticCredits;
|
||||
for (let i = 0; i < staticCredits.length; ++i) {
|
||||
const staticCredit = staticCredits[i];
|
||||
const creditCollection = staticCredit.showOnScreen
|
||||
? screenCredits
|
||||
: lightboxCredits;
|
||||
|
||||
if (
|
||||
staticCredit.isIon() &&
|
||||
Credit.equals(CreditDisplay.cesiumCredit, this._cesiumCredit)
|
||||
) {
|
||||
// If this is an ion logo credit from the ion server,
|
||||
// make sure to de-duplicate with the default ion credit
|
||||
continue;
|
||||
}
|
||||
|
||||
setCredit(this, creditCollection, staticCredit, Number.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (!Credit.equals(CreditDisplay.cesiumCredit, this._cesiumCredit)) {
|
||||
this._cesiumCredit = Credit.clone(CreditDisplay.cesiumCredit);
|
||||
}
|
||||
this._currentCesiumCredit = this._cesiumCredit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the credit display to the end of frame state, displaying credits from the last frame in the credit container.
|
||||
*/
|
||||
CreditDisplay.prototype.endFrame = function () {
|
||||
const screenCredits = this._currentFrameCredits.screenCredits.values;
|
||||
displayCredits(
|
||||
this._screenContainer,
|
||||
screenCredits,
|
||||
this._delimiter,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const lightboxCredits = this._currentFrameCredits.lightboxCredits.values;
|
||||
this._expandLink.style.display =
|
||||
lightboxCredits.length > 0 ? "inline" : "none";
|
||||
displayCredits(this._creditList, lightboxCredits, undefined, "li");
|
||||
|
||||
swapCesiumCredit(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the resources held by this object. Destroying an object allows for deterministic
|
||||
* release of 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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*/
|
||||
CreditDisplay.prototype.destroy = function () {
|
||||
this._lightbox.removeEventListener("click", this._hideLightbox, false);
|
||||
|
||||
this.container.removeChild(this._cesiumCreditContainer);
|
||||
this.container.removeChild(this._screenContainer);
|
||||
this.container.removeChild(this._expandLink);
|
||||
this.viewport.removeChild(this._lightbox);
|
||||
|
||||
return destroyObject(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if this object was destroyed; otherwise, false.
|
||||
* <br /><br />
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
||||
*/
|
||||
CreditDisplay.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
CreditDisplay._cesiumCredit = undefined;
|
||||
CreditDisplay._cesiumCreditInitialized = false;
|
||||
|
||||
let defaultCredit;
|
||||
function getDefaultCredit() {
|
||||
if (!defined(defaultCredit)) {
|
||||
let logo = buildModuleUrl("Assets/Images/ion-credit.png");
|
||||
|
||||
// When hosting in a WebView, the base URL scheme is file:// or ms-appx-web://
|
||||
// which is stripped out from the Credit's <img> tag; use the full path instead
|
||||
if (
|
||||
logo.indexOf("http://") !== 0 &&
|
||||
logo.indexOf("https://") !== 0 &&
|
||||
logo.indexOf("data:") !== 0
|
||||
) {
|
||||
const logoUrl = new Uri(logo);
|
||||
logo = logoUrl.path();
|
||||
}
|
||||
|
||||
defaultCredit = new Credit(
|
||||
`<a href="https://cesium.com/" target="_blank"><img src="${logo}" style="vertical-align: -7px" title="Cesium ion"/></a>`,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if (!CreditDisplay._cesiumCreditInitialized) {
|
||||
CreditDisplay._cesiumCredit = defaultCredit;
|
||||
CreditDisplay._cesiumCreditInitialized = true;
|
||||
}
|
||||
return defaultCredit;
|
||||
}
|
||||
|
||||
Object.defineProperties(CreditDisplay, {
|
||||
/**
|
||||
* Gets or sets the Cesium logo credit.
|
||||
* @memberof CreditDisplay
|
||||
* @type {Credit}
|
||||
*/
|
||||
cesiumCredit: {
|
||||
get: function () {
|
||||
getDefaultCredit();
|
||||
return CreditDisplay._cesiumCredit;
|
||||
},
|
||||
set: function (value) {
|
||||
CreditDisplay._cesiumCredit = value;
|
||||
CreditDisplay._cesiumCreditInitialized = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
CreditDisplay.CreditDisplayElement = CreditDisplayElement;
|
||||
export default CreditDisplay;
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
import BoxGeometry from "../Core/BoxGeometry.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import GeometryPipeline from "../Core/GeometryPipeline.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import VertexFormat from "../Core/VertexFormat.js";
|
||||
import BufferUsage from "../Renderer/BufferUsage.js";
|
||||
import CubeMap from "../Renderer/CubeMap.js";
|
||||
import DrawCommand from "../Renderer/DrawCommand.js";
|
||||
import loadCubeMap from "../Renderer/loadCubeMap.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 SkyBoxFS from "../Shaders/SkyBoxFS.js";
|
||||
import SkyBoxVS from "../Shaders/SkyBoxVS.js";
|
||||
import CubeMapPanoramaVS from "../Shaders/CubeMapPanoramaVS.js";
|
||||
import BlendingState from "./BlendingState.js";
|
||||
import SceneMode from "./SceneMode.js";
|
||||
import Pass from "../Renderer/Pass.js";
|
||||
import Credit from "../Core/Credit.js";
|
||||
|
||||
/**
|
||||
* @typedef {object} CubeMapPanorama.ConstructorOptions
|
||||
*
|
||||
* Initialization options for the CubeMapPanorama constructor
|
||||
*
|
||||
* @property {object} [options.sources] The source URL or <code>Image</code> object for each of the six cube map faces. See the example below.
|
||||
* @property {Matrix3} [options.transform] A 3x3 transformation matrix that defines the panorama’s orientation. If not specified, the default orientation is defined using the True Equator Mean Equinox (TEME) axes.
|
||||
* @property {boolean} [options.show=true] Determines if this primitive will be shown.
|
||||
* @property {Credit|string} [options.credit] A credit for the panorama, which is displayed on the canvas.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A {@link Panorama} that displays imagery in cube map format in a scene.
|
||||
* <p>
|
||||
* This is only supported in 3D. The cube map panorama is faded out when morphing to 2D or Columbus view. The size of
|
||||
* the cube map panorama must not exceed {@link Scene#maximumSkyBoxSize}.
|
||||
* </p>
|
||||
*
|
||||
* @alias CubeMapPanorama
|
||||
* @constructor
|
||||
*
|
||||
* @param {CubeMapPanorama.ConstructorOptions} options Object describing initialization options
|
||||
*
|
||||
* @example
|
||||
* const modelMatrix = Cesium.Matrix4.getMatrix3(
|
||||
* Cesium.Transforms.localFrameToFixedFrameGenerator("north", "down")(
|
||||
* Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
|
||||
* Cesium.Ellipsoid.default
|
||||
* ),
|
||||
* new Cesium.Matrix3()
|
||||
* );
|
||||
*
|
||||
*
|
||||
* scene.primitives.add(new Cesium.CubeMapPanorama({
|
||||
* sources : {
|
||||
* positiveX : 'cubemap_px.png',
|
||||
* negativeX : 'cubemap_nx.png',
|
||||
* positiveY : 'cubemap_py.png',
|
||||
* negativeY : 'cubemap_ny.png',
|
||||
* positiveZ : 'cubemap_pz.png',
|
||||
* negativeZ : 'cubemap_nz.png'
|
||||
* }
|
||||
* transform: modelMatrix,
|
||||
* }));
|
||||
*
|
||||
* @see SkyBox
|
||||
*
|
||||
* @demo {@link https://sandcastle.cesium.com/index.html?id=panorama|Cesium Sandcastle Panorama}
|
||||
*/
|
||||
function CubeMapPanorama(options) {
|
||||
/**
|
||||
* The sources used to create the cube map faces: an object
|
||||
* with <code>positiveX</code>, <code>negativeX</code>, <code>positiveY</code>,
|
||||
* <code>negativeY</code>, <code>positiveZ</code>, and <code>negativeZ</code> properties.
|
||||
* These can be either URLs or <code>Image</code> objects.
|
||||
*
|
||||
* @type {object}
|
||||
* @default undefined
|
||||
*/
|
||||
this.sources = options.sources;
|
||||
this._sources = undefined;
|
||||
|
||||
this._transform = options.transform;
|
||||
|
||||
/**
|
||||
* Determines if the cube map panorama will be shown.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.show = options.show ?? true;
|
||||
|
||||
this._returnCommand = options.returnCommand ?? false;
|
||||
this._addToPanoramaCommandList = !this._returnCommand;
|
||||
|
||||
this._command = new DrawCommand({
|
||||
modelMatrix: Matrix4.clone(Matrix4.IDENTITY),
|
||||
owner: this,
|
||||
// render before everything else
|
||||
pass: Pass.ENVIRONMENT,
|
||||
});
|
||||
this._cubeMap = undefined;
|
||||
|
||||
this._attributeLocations = undefined;
|
||||
this._useHdr = undefined;
|
||||
this._hasError = false;
|
||||
this._error = undefined;
|
||||
|
||||
// Credit specified by the user.
|
||||
let credit = options.credit;
|
||||
if (typeof credit === "string") {
|
||||
credit = new Credit(credit);
|
||||
}
|
||||
this._credit = credit;
|
||||
}
|
||||
|
||||
Object.defineProperties(CubeMapPanorama.prototype, {
|
||||
/**
|
||||
* Gets the transform of the panorama. If undefined, the default orientation uses the True Equator Mean Equinox (TEME) axes.
|
||||
* @memberof CubeMapPanorama.prototype
|
||||
* @type {Matrix3}
|
||||
* @readonly
|
||||
*/
|
||||
transform: {
|
||||
get: function () {
|
||||
return this._transform;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credits of the panorama.
|
||||
* @memberof CubeMapPanorama.prototype
|
||||
* @type {Credit}
|
||||
* @readonly
|
||||
*/
|
||||
credit: {
|
||||
get: function () {
|
||||
return defined(this._credit) ? this._credit : undefined;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
|
||||
* @exception {DeveloperError} this.sources properties must all be the same type.
|
||||
*/
|
||||
CubeMapPanorama.prototype.update = function (frameState, useHdr) {
|
||||
const that = this;
|
||||
const { mode, passes, context, panoramaCommandList } = frameState;
|
||||
|
||||
if (!this.show) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (mode !== SceneMode.SCENE3D && mode !== SceneMode.MORPHING) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The cube map panorama is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
|
||||
if (!passes.render) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Throw any errors that had previously occurred asynchronously so they aren't
|
||||
// ignored when running. See https://github.com/CesiumGS/cesium/pull/12307
|
||||
if (this._hasError) {
|
||||
const error = this._error;
|
||||
this._hasError = false;
|
||||
this._error = undefined;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (this._sources !== this.sources) {
|
||||
this._sources = this.sources;
|
||||
const sources = this.sources;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.defined("this.sources", sources);
|
||||
if (
|
||||
Object.values(CubeMap.FaceName).some(
|
||||
(faceName) => !defined(sources[faceName]),
|
||||
)
|
||||
) {
|
||||
throw new DeveloperError(
|
||||
"this.sources must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.",
|
||||
);
|
||||
}
|
||||
|
||||
const sourceType = typeof sources.positiveX;
|
||||
if (
|
||||
Object.values(CubeMap.FaceName).some(
|
||||
(faceName) => typeof sources[faceName] !== sourceType,
|
||||
)
|
||||
) {
|
||||
throw new DeveloperError(
|
||||
"this.sources properties must all be the same type.",
|
||||
);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (typeof sources.positiveX === "string") {
|
||||
// Given urls for cube-map images. Load them.
|
||||
loadCubeMap(context, this._sources)
|
||||
.then(function (cubeMap) {
|
||||
that._cubeMap = that._cubeMap && that._cubeMap.destroy();
|
||||
that._cubeMap = cubeMap;
|
||||
})
|
||||
.catch((error) => {
|
||||
// Defer throwing the error until the next call to update to prevent
|
||||
// test from failing in `afterAll` if this is rejected after the test
|
||||
// using the Skybox ends. See https://github.com/CesiumGS/cesium/pull/12307
|
||||
this._hasError = true;
|
||||
this._error = error;
|
||||
});
|
||||
} else {
|
||||
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
|
||||
this._cubeMap = new CubeMap({
|
||||
context: context,
|
||||
source: sources,
|
||||
});
|
||||
}
|
||||
this._addToPanoramaCommandList = true;
|
||||
}
|
||||
|
||||
const command = this._command;
|
||||
|
||||
if (!defined(command.vertexArray)) {
|
||||
command.uniformMap = {
|
||||
u_cubeMap: function () {
|
||||
return that._cubeMap;
|
||||
},
|
||||
u_cubeMapPanoramaTransform: function () {
|
||||
return that._transform;
|
||||
},
|
||||
};
|
||||
|
||||
const geometry = BoxGeometry.createGeometry(
|
||||
BoxGeometry.fromDimensions({
|
||||
dimensions: new Cartesian3(2.0, 2.0, 2.0),
|
||||
vertexFormat: VertexFormat.POSITION_ONLY,
|
||||
}),
|
||||
);
|
||||
const attributeLocations = (this._attributeLocations =
|
||||
GeometryPipeline.createAttributeLocations(geometry));
|
||||
|
||||
command.vertexArray = VertexArray.fromGeometry({
|
||||
context: context,
|
||||
geometry: geometry,
|
||||
attributeLocations: attributeLocations,
|
||||
bufferUsage: BufferUsage.STATIC_DRAW,
|
||||
});
|
||||
|
||||
// no depth test/write
|
||||
command.renderState = RenderState.fromCache({
|
||||
depthTest: { enabled: false },
|
||||
depthMask: false,
|
||||
blending: BlendingState.ALPHA_BLEND,
|
||||
});
|
||||
this._addToPanoramaCommandList = true;
|
||||
}
|
||||
|
||||
if (!defined(command.shaderProgram) || this._useHdr !== useHdr) {
|
||||
const fs = new ShaderSource({
|
||||
defines: [useHdr ? "HDR" : ""],
|
||||
sources: [SkyBoxFS],
|
||||
});
|
||||
command.shaderProgram = ShaderProgram.fromCache({
|
||||
context: context,
|
||||
//vertexShaderSource: SkyBoxVS,
|
||||
vertexShaderSource: defined(this._transform)
|
||||
? CubeMapPanoramaVS
|
||||
: SkyBoxVS,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: this._attributeLocations,
|
||||
});
|
||||
this._useHdr = useHdr;
|
||||
this._addToPanoramaCommandList = true;
|
||||
}
|
||||
|
||||
if (!defined(this._cubeMap)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.show && defined(this._credit) && !this._returnCommand) {
|
||||
const creditDisplay = frameState.creditDisplay;
|
||||
creditDisplay.addCreditToNextFrame(this._credit);
|
||||
}
|
||||
|
||||
if (this._returnCommand) {
|
||||
return command;
|
||||
}
|
||||
|
||||
if (this._addToPanoramaCommandList) {
|
||||
panoramaCommandList.push(command);
|
||||
this._addToPanoramaCommandList = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 CubeMapPanorama#destroy
|
||||
*/
|
||||
CubeMapPanorama.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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* cubeMapPanorama = cubeMapPanorama && cubeMapPanorama.destroy();
|
||||
*
|
||||
* @see CubeMapPanorama#isDestroyed
|
||||
*/
|
||||
CubeMapPanorama.prototype.destroy = function () {
|
||||
const command = this._command;
|
||||
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
|
||||
command.shaderProgram =
|
||||
command.shaderProgram && command.shaderProgram.destroy();
|
||||
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
|
||||
export default CubeMapPanorama;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// @ts-check
|
||||
|
||||
import WebGLConstants from "../Core/WebGLConstants.js";
|
||||
|
||||
/**
|
||||
* Determines which triangles, if any, are culled.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const CullFace = {
|
||||
/**
|
||||
* Front-facing triangles are culled.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
FRONT: WebGLConstants.FRONT,
|
||||
|
||||
/**
|
||||
* Back-facing triangles are culled.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
BACK: WebGLConstants.BACK,
|
||||
|
||||
/**
|
||||
* Both front-facing and back-facing triangles are culled.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
FRONT_AND_BACK: WebGLConstants.FRONT_AND_BACK,
|
||||
};
|
||||
|
||||
Object.freeze(CullFace);
|
||||
|
||||
export default CullFace;
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
import Cartesian2 from "../Core/Cartesian2.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/**
|
||||
* <div class="notice">
|
||||
* A cloud is created and its initial properties are set by calling {@link CloudCollection#add}.
|
||||
* and {@link CloudCollection#remove}. Do not call the constructor directly.
|
||||
* </div>
|
||||
* A cumulus cloud billboard positioned in the 3D scene, that is created and rendered using a {@link CloudCollection}.
|
||||
* <br /><br />
|
||||
* <div align='center'>
|
||||
* <img src='Images/CumulusCloud.png' width='400' height='300' /><br />
|
||||
* Example cumulus clouds
|
||||
* </div>
|
||||
* @alias CumulusCloud
|
||||
*
|
||||
* @performance Similar to {@link Billboard}, reading a property, e.g., {@link CumulusCloud#show},
|
||||
* takes constant time. Assigning to a property is constant time but results in
|
||||
* CPU to GPU traffic when {@link CloudCollection#update} is called. The per-cloud traffic is
|
||||
* the same regardless of how many properties were updated. If most clouds in a collection need to be
|
||||
* updated, it may be more efficient to clear the collection with {@link CloudCollection#removeAll}
|
||||
* and add new clouds instead of modifying each one.
|
||||
*
|
||||
* @see CloudCollection
|
||||
* @see CloudCollection#add
|
||||
*
|
||||
* @internalConstructor
|
||||
* @class
|
||||
*
|
||||
* @demo {@link https://sandcastle.cesium.com/index.html?id=cloud-parameters|Cesium Sandcastle Cloud Parameters Demo}
|
||||
*/
|
||||
function CumulusCloud(options, cloudCollection) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
this._show = options.show ?? true;
|
||||
|
||||
this._position = Cartesian3.clone(options.position ?? Cartesian3.ZERO);
|
||||
|
||||
if (!defined(options.scale) && defined(options.maximumSize)) {
|
||||
this._maximumSize = Cartesian3.clone(options.maximumSize);
|
||||
this._scale = new Cartesian2(this._maximumSize.x, this._maximumSize.y);
|
||||
} else {
|
||||
this._scale = Cartesian2.clone(options.scale ?? new Cartesian2(20.0, 12.0));
|
||||
|
||||
const defaultMaxSize = new Cartesian3(
|
||||
this._scale.x,
|
||||
this._scale.y,
|
||||
Math.min(this._scale.x, this._scale.y) / 1.5,
|
||||
);
|
||||
this._maximumSize = Cartesian3.clone(options.maximumSize ?? defaultMaxSize);
|
||||
}
|
||||
|
||||
this._slice = options.slice ?? -1.0;
|
||||
this._color = Color.clone(options.color ?? Color.WHITE);
|
||||
this._brightness = options.brightness ?? 1.0;
|
||||
this._cloudCollection = cloudCollection;
|
||||
this._index = -1; // Used by CloudCollection
|
||||
}
|
||||
|
||||
const SHOW_INDEX = (CumulusCloud.SHOW_INDEX = 0);
|
||||
const POSITION_INDEX = (CumulusCloud.POSITION_INDEX = 1);
|
||||
const SCALE_INDEX = (CumulusCloud.SCALE_INDEX = 2);
|
||||
const MAXIMUM_SIZE_INDEX = (CumulusCloud.MAXIMUM_SIZE_INDEX = 3);
|
||||
const SLICE_INDEX = (CumulusCloud.SLICE_INDEX = 4);
|
||||
const BRIGHTNESS_INDEX = (CumulusCloud.BRIGHTNESS_INDEX = 5);
|
||||
const COLOR_INDEX = (CumulusCloud.COLOR_INDEX = 6);
|
||||
CumulusCloud.NUMBER_OF_PROPERTIES = 7;
|
||||
|
||||
function makeDirty(cloud, propertyChanged) {
|
||||
const cloudCollection = cloud._cloudCollection;
|
||||
if (defined(cloudCollection)) {
|
||||
cloudCollection._updateCloud(cloud, propertyChanged);
|
||||
cloud._dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(CumulusCloud.prototype, {
|
||||
/**
|
||||
* Determines if this cumulus cloud will be shown. Use this to hide or show a cloud, instead
|
||||
* of removing it and re-adding it to the collection.
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
show: {
|
||||
get: function () {
|
||||
return this._show;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.bool("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (this._show !== value) {
|
||||
this._show = value;
|
||||
makeDirty(this, SHOW_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets or sets the Cartesian position of this cumulus cloud.
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {Cartesian3}
|
||||
*/
|
||||
position: {
|
||||
get: function () {
|
||||
return this._position;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.object("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const position = this._position;
|
||||
if (!Cartesian3.equals(position, value)) {
|
||||
Cartesian3.clone(value, position);
|
||||
makeDirty(this, POSITION_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* <p>Gets or sets the scale of the cumulus cloud billboard in meters.
|
||||
* The <code>scale</code> property will affect the size of the billboard,
|
||||
* but not the cloud's actual appearance.</p>
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'><tr>
|
||||
* <td align='center'>
|
||||
* <code>cloud.scale = new Cesium.Cartesian2(12, 8);</code><br/>
|
||||
* <img src='Images/CumulusCloud.scalex12y8.png' width='250' height='158' />
|
||||
* </td>
|
||||
* <td align='center'>
|
||||
* <code>cloud.scale = new Cesium.Cartesian2(24, 10);</code><br/>
|
||||
* <img src='Images/CumulusCloud.scalex24y10.png' width='250' height='158' />
|
||||
* </td>
|
||||
* </tr></table>
|
||||
* </div>
|
||||
*
|
||||
* <p>To modify the cloud's appearance, modify its <code>maximumSize</code>
|
||||
* and <code>slice</code> properties.</p>
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {Cartesian2}
|
||||
*
|
||||
* @see CumulusCloud#maximumSize
|
||||
* @see CumulusCloud#slice
|
||||
*/
|
||||
scale: {
|
||||
get: function () {
|
||||
return this._scale;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.object("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const scale = this._scale;
|
||||
if (!Cartesian2.equals(scale, value)) {
|
||||
Cartesian2.clone(value, scale);
|
||||
makeDirty(this, SCALE_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* <p>Gets or sets the maximum size of the cumulus cloud rendered on the billboard.
|
||||
* This defines a maximum ellipsoid volume that the cloud can appear in.
|
||||
* Rather than guaranteeing a specific size, this specifies a boundary for the
|
||||
* cloud to appear in, and changing it can affect the shape of the cloud.</p>
|
||||
* <p>Changing the z-value of <code>maximumSize</code> has the most dramatic effect
|
||||
* on the cloud's appearance because it changes the depth of the cloud, and thus the
|
||||
* positions at which the cloud-shaping texture is sampled.</p>
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'>
|
||||
* <tr>
|
||||
* <td align='center'>
|
||||
* <code>cloud.maximumSize = new Cesium.Cartesian3(14, 9, 10);</code><br/>
|
||||
* <img src='Images/CumulusCloud.maximumSizex14y9z10.png' width='250' height='158' />
|
||||
* </td>
|
||||
* <td align='center'>
|
||||
* <code>cloud.maximumSize.x = 25;</code><br/>
|
||||
* <img src='Images/CumulusCloud.maximumSizex25.png' width='250' height='158' />
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td align='center'>
|
||||
* <code>cloud.maximumSize.y = 5;</code><br/>
|
||||
* <img src='Images/CumulusCloud.maximumSizey5.png' width='250' height='158' />
|
||||
* </td>
|
||||
* <td align='center'>
|
||||
* <code>cloud.maximumSize.z = 17;</code><br/>
|
||||
* <img src='Images/CumulusCloud.maximumSizez17.png' width='250' height='158' />
|
||||
* </td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* </div>
|
||||
*
|
||||
* <p>To modify the billboard's actual size, modify the cloud's <code>scale</code> property.</p>
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {Cartesian3}
|
||||
*
|
||||
* @see CumulusCloud#scale
|
||||
*/
|
||||
maximumSize: {
|
||||
get: function () {
|
||||
return this._maximumSize;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.object("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const maximumSize = this._maximumSize;
|
||||
if (!Cartesian3.equals(maximumSize, value)) {
|
||||
Cartesian3.clone(value, maximumSize);
|
||||
makeDirty(this, MAXIMUM_SIZE_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Sets the color of the cloud
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
color: {
|
||||
get: function () {
|
||||
return this._color;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.object("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const color = this._color;
|
||||
if (!Color.equals(color, value)) {
|
||||
Color.clone(value, color);
|
||||
makeDirty(this, COLOR_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* <p>Gets or sets the "slice" of the cloud that is rendered on the billboard, i.e.
|
||||
* the specific cross-section of the cloud chosen for the billboard's appearance.
|
||||
* Given a value between 0 and 1, the slice specifies how deeply into the cloud
|
||||
* to intersect based on its maximum size in the z-direction.</p>
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'><tr>
|
||||
* <td align='center'><code>cloud.slice = 0.32;</code><br/><img src='Images/CumulusCloud.slice0.32.png' width='250' height='158' /></td>
|
||||
* <td align='center'><code>cloud.slice = 0.5;</code><br/><img src='Images/CumulusCloud.slice0.5.png' width='250' height='158' /></td>
|
||||
* <td align='center'><code>cloud.slice = 0.6;</code><br/><img src='Images/CumulusCloud.slice0.6.png' width='250' height='158' /></td>
|
||||
* </tr></table>
|
||||
* </div>
|
||||
*
|
||||
* <br />
|
||||
* <p>Due to the nature in which this slice is calculated,
|
||||
* values below <code>0.2</code> may result in cross-sections that are too small,
|
||||
* and the edge of the ellipsoid will be visible. Similarly, values above <code>0.7</code>
|
||||
* will cause the cloud to appear smaller. Values outside the range <code>[0.1, 0.9]</code>
|
||||
* should be avoided entirely because they do not produce desirable results.</p>
|
||||
*
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'><tr>
|
||||
* <td align='center'><code>cloud.slice = 0.08;</code><br/><img src='Images/CumulusCloud.slice0.08.png' width='250' height='158' /></td>
|
||||
* <td align='center'><code>cloud.slice = 0.8;</code><br/><img src='Images/CumulusCloud.slice0.8.png' width='250' height='158' /></td>
|
||||
* </tr></table>
|
||||
* </div>
|
||||
*
|
||||
* <p>If <code>slice</code> is set to a negative number, the cloud will not render a cross-section.
|
||||
* Instead, it will render the outside of the ellipsoid that is visible. For clouds with
|
||||
* small values of `maximumSize.z`, this can produce good-looking results, but for larger
|
||||
* clouds, this can result in a cloud that is undesirably warped to the ellipsoid volume.</p>
|
||||
*
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'><tr>
|
||||
* <td align='center'>
|
||||
* <code>cloud.slice = -1.0;<br/>cloud.maximumSize.z = 18;</code><br/>
|
||||
* <img src='Images/CumulusCloud.slice-1z18.png' width='250' height='158' />
|
||||
* </td>
|
||||
* <td align='center'>
|
||||
* <code>cloud.slice = -1.0;<br/>cloud.maximumSize.z = 30;</code><br/>
|
||||
* <img src='Images/CumulusCloud.slice-1z30.png' width='250' height='158' /></td>
|
||||
* </tr></table>
|
||||
* </div>
|
||||
*
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {number}
|
||||
* @default -1.0
|
||||
*/
|
||||
slice: {
|
||||
get: function () {
|
||||
return this._slice;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const slice = this._slice;
|
||||
if (slice !== value) {
|
||||
this._slice = value;
|
||||
makeDirty(this, SLICE_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets or sets the brightness of the cloud. This can be used to give clouds
|
||||
* a darker, grayer appearance.
|
||||
* <br /><br />
|
||||
* <div align='center'>
|
||||
* <table border='0' cellpadding='5'><tr>
|
||||
* <td align='center'><code>cloud.brightness = 1.0;</code><br/><img src='Images/CumulusCloud.brightness1.png' width='250' height='158' /></td>
|
||||
* <td align='center'><code>cloud.brightness = 0.6;</code><br/><img src='Images/CumulusCloud.brightness0.6.png' width='250' height='158' /></td>
|
||||
* <td align='center'><code>cloud.brightness = 0.0;</code><br/><img src='Images/CumulusCloud.brightness0.png' width='250' height='158' /></td>
|
||||
* </tr></table>
|
||||
* </div>
|
||||
* @memberof CumulusCloud.prototype
|
||||
* @type {number}
|
||||
* @default 1.0
|
||||
*/
|
||||
brightness: {
|
||||
get: function () {
|
||||
return this._brightness;
|
||||
},
|
||||
set: function (value) {
|
||||
//>>includeStart('debug', pragmas.debug)
|
||||
Check.typeOf.number("value", value);
|
||||
//>>includeEnd('debug');
|
||||
|
||||
const brightness = this._brightness;
|
||||
if (brightness !== value) {
|
||||
this._brightness = value;
|
||||
makeDirty(this, BRIGHTNESS_INDEX);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
CumulusCloud.prototype._destroy = function () {
|
||||
this._cloudCollection = undefined;
|
||||
};
|
||||
|
||||
export default CumulusCloud;
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import Appearance from "./Appearance.js";
|
||||
|
||||
/**
|
||||
* Visualizes a vertex attribute by displaying it as a color for debugging.
|
||||
* <p>
|
||||
* Components for well-known unit-length vectors, i.e., <code>normal</code>,
|
||||
* <code>tangent</code>, and <code>bitangent</code>, are scaled and biased
|
||||
* from [-1.0, 1.0] to (-1.0, 1.0).
|
||||
* </p>
|
||||
*
|
||||
* @alias DebugAppearance
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {string} options.attributeName The name of the attribute to visualize.
|
||||
* @param {boolean} [options.perInstanceAttribute=false] Boolean that determines whether this attribute is a per-instance geometry attribute.
|
||||
* @param {string} [options.glslDatatype='vec3'] The GLSL datatype of the attribute. Supported datatypes are <code>float</code>, <code>vec2</code>, <code>vec3</code>, and <code>vec4</code>.
|
||||
* @param {string} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
|
||||
* @param {string} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
|
||||
* @param {object} [options.renderState] Optional render state to override the default render state.
|
||||
*
|
||||
* @exception {DeveloperError} options.glslDatatype must be float, vec2, vec3, or vec4.
|
||||
*
|
||||
* @example
|
||||
* const primitive = new Cesium.Primitive({
|
||||
* geometryInstances : // ...
|
||||
* appearance : new Cesium.DebugAppearance({
|
||||
* attributeName : 'normal'
|
||||
* })
|
||||
* });
|
||||
*/
|
||||
function DebugAppearance(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
const attributeName = options.attributeName;
|
||||
let perInstanceAttribute = options.perInstanceAttribute;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(attributeName)) {
|
||||
throw new DeveloperError("options.attributeName is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
if (!defined(perInstanceAttribute)) {
|
||||
perInstanceAttribute = false;
|
||||
}
|
||||
|
||||
let glslDatatype = options.glslDatatype ?? "vec3";
|
||||
const varyingName = `v_${attributeName}`;
|
||||
let getColor;
|
||||
|
||||
// Well-known normalized vector attributes in VertexFormat
|
||||
if (
|
||||
attributeName === "normal" ||
|
||||
attributeName === "tangent" ||
|
||||
attributeName === "bitangent"
|
||||
) {
|
||||
getColor = `vec4 getColor() { return vec4((${varyingName} + vec3(1.0)) * 0.5, 1.0); }\n`;
|
||||
} else {
|
||||
// All other attributes, both well-known and custom
|
||||
if (attributeName === "st") {
|
||||
glslDatatype = "vec2";
|
||||
}
|
||||
|
||||
switch (glslDatatype) {
|
||||
case "float":
|
||||
getColor = `vec4 getColor() { return vec4(vec3(${varyingName}), 1.0); }\n`;
|
||||
break;
|
||||
case "vec2":
|
||||
getColor = `vec4 getColor() { return vec4(${varyingName}, 0.0, 1.0); }\n`;
|
||||
break;
|
||||
case "vec3":
|
||||
getColor = `vec4 getColor() { return vec4(${varyingName}, 1.0); }\n`;
|
||||
break;
|
||||
case "vec4":
|
||||
getColor = `vec4 getColor() { return ${varyingName}; }\n`;
|
||||
break;
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
default:
|
||||
throw new DeveloperError(
|
||||
"options.glslDatatype must be float, vec2, vec3, or vec4.",
|
||||
);
|
||||
//>>includeEnd('debug');
|
||||
}
|
||||
}
|
||||
|
||||
const vs =
|
||||
`${
|
||||
"in vec3 position3DHigh;\n" +
|
||||
"in vec3 position3DLow;\n" +
|
||||
"in float batchId;\n"
|
||||
}${
|
||||
perInstanceAttribute ? "" : `in ${glslDatatype} ${attributeName};\n`
|
||||
}out ${glslDatatype} ${varyingName};\n` +
|
||||
`void main()\n` +
|
||||
`{\n` +
|
||||
`vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n${
|
||||
perInstanceAttribute
|
||||
? `${varyingName} = czm_batchTable_${attributeName}(batchId);\n`
|
||||
: `${varyingName} = ${attributeName};\n`
|
||||
}gl_Position = czm_modelViewProjectionRelativeToEye * p;\n` +
|
||||
`}`;
|
||||
const fs =
|
||||
`in ${glslDatatype} ${varyingName};\n${getColor}\n` +
|
||||
`void main()\n` +
|
||||
`{\n` +
|
||||
`out_FragColor = getColor();\n` +
|
||||
`}`;
|
||||
|
||||
/**
|
||||
* This property is part of the {@link Appearance} interface, but is not
|
||||
* used by {@link DebugAppearance} since a fully custom fragment shader is used.
|
||||
*
|
||||
* @type Material
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
this.material = undefined;
|
||||
|
||||
/**
|
||||
* When <code>true</code>, the geometry is expected to appear translucent.
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
this.translucent = options.translucent ?? false;
|
||||
|
||||
this._vertexShaderSource = options.vertexShaderSource ?? vs;
|
||||
this._fragmentShaderSource = options.fragmentShaderSource ?? fs;
|
||||
this._renderState = Appearance.getDefaultRenderState(
|
||||
false,
|
||||
false,
|
||||
options.renderState,
|
||||
);
|
||||
this._closed = options.closed ?? false;
|
||||
|
||||
// Non-derived members
|
||||
|
||||
this._attributeName = attributeName;
|
||||
this._glslDatatype = glslDatatype;
|
||||
}
|
||||
|
||||
Object.defineProperties(DebugAppearance.prototype, {
|
||||
/**
|
||||
* The GLSL source code for the vertex shader.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
vertexShaderSource: {
|
||||
get: function () {
|
||||
return this._vertexShaderSource;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The GLSL source code for the fragment shader. The full fragment shader
|
||||
* source is built procedurally taking into account the {@link DebugAppearance#material}.
|
||||
* Use {@link DebugAppearance#getFragmentShaderSource} to get the full source.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
fragmentShaderSource: {
|
||||
get: function () {
|
||||
return this._fragmentShaderSource;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The WebGL fixed-function state to use when rendering the geometry.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {object}
|
||||
* @readonly
|
||||
*/
|
||||
renderState: {
|
||||
get: function () {
|
||||
return this._renderState;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* When <code>true</code>, the geometry is expected to be closed.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
closed: {
|
||||
get: function () {
|
||||
return this._closed;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The name of the attribute being visualized.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
attributeName: {
|
||||
get: function () {
|
||||
return this._attributeName;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The GLSL datatype of the attribute being visualized.
|
||||
*
|
||||
* @memberof DebugAppearance.prototype
|
||||
*
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
glslDatatype: {
|
||||
get: function () {
|
||||
return this._glslDatatype;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the full GLSL fragment shader source, which for {@link DebugAppearance} is just
|
||||
* {@link DebugAppearance#fragmentShaderSource}.
|
||||
*
|
||||
* @function
|
||||
*
|
||||
* @returns {string} The full GLSL fragment shader source.
|
||||
*/
|
||||
DebugAppearance.prototype.getFragmentShaderSource =
|
||||
Appearance.prototype.getFragmentShaderSource;
|
||||
|
||||
/**
|
||||
* Determines if the geometry is translucent based on {@link DebugAppearance#translucent}.
|
||||
*
|
||||
* @function
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if the appearance is translucent.
|
||||
*/
|
||||
DebugAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
|
||||
|
||||
/**
|
||||
* Creates a render state. This is not the final render state instance; instead,
|
||||
* it can contain a subset of render state properties identical to the render state
|
||||
* created in the context.
|
||||
*
|
||||
* @function
|
||||
*
|
||||
* @returns {object} The render state.
|
||||
*/
|
||||
DebugAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
|
||||
export default DebugAppearance;
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import ColorGeometryInstanceAttribute from "../Core/ColorGeometryInstanceAttribute.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import FrustumGeometry from "../Core/FrustumGeometry.js";
|
||||
import FrustumOutlineGeometry from "../Core/FrustumOutlineGeometry.js";
|
||||
import GeometryInstance from "../Core/GeometryInstance.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import OrthographicFrustum from "../Core/OrthographicFrustum.js";
|
||||
import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
|
||||
import PerspectiveFrustum from "../Core/PerspectiveFrustum.js";
|
||||
import PerspectiveOffCenterFrustum from "../Core/PerspectiveOffCenterFrustum.js";
|
||||
import Quaternion from "../Core/Quaternion.js";
|
||||
import PerInstanceColorAppearance from "./PerInstanceColorAppearance.js";
|
||||
import Primitive from "./Primitive.js";
|
||||
|
||||
/**
|
||||
* Draws the outline of the camera's view frustum.
|
||||
*
|
||||
* @alias DebugCameraPrimitive
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {Camera} options.camera The camera.
|
||||
* @param {number[]} [options.frustumSplits] Distances to the near and far planes of the camera frustums. This overrides the camera's frustum near and far values.
|
||||
* @param {Color} [options.color=Color.CYAN] The color of the debug outline.
|
||||
* @param {boolean} [options.updateOnChange=true] Whether the primitive updates when the underlying camera changes.
|
||||
* @param {boolean} [options.show=true] Determines if this primitive will be shown.
|
||||
* @param {object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}.
|
||||
*
|
||||
* @example
|
||||
* primitives.add(new Cesium.DebugCameraPrimitive({
|
||||
* camera : camera,
|
||||
* color : Cesium.Color.YELLOW
|
||||
* }));
|
||||
*/
|
||||
function DebugCameraPrimitive(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(options.camera)) {
|
||||
throw new DeveloperError("options.camera is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._camera = options.camera;
|
||||
this._frustumSplits = options.frustumSplits;
|
||||
this._color = options.color ?? Color.CYAN;
|
||||
this._updateOnChange = options.updateOnChange ?? true;
|
||||
|
||||
/**
|
||||
* Determines if this primitive will be shown.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.show = options.show ?? true;
|
||||
|
||||
/**
|
||||
* User-defined value returned when the primitive is picked.
|
||||
*
|
||||
* @type {*}
|
||||
* @default undefined
|
||||
*
|
||||
* @see Scene#pick
|
||||
*/
|
||||
this.id = options.id;
|
||||
this._id = undefined;
|
||||
|
||||
this._outlinePrimitives = [];
|
||||
this._planesPrimitives = [];
|
||||
}
|
||||
|
||||
const scratchRight = new Cartesian3();
|
||||
const scratchRotation = new Matrix3();
|
||||
const scratchOrientation = new Quaternion();
|
||||
const scratchPerspective = new PerspectiveFrustum();
|
||||
const scratchPerspectiveOffCenter = new PerspectiveOffCenterFrustum();
|
||||
const scratchOrthographic = new OrthographicFrustum();
|
||||
const scratchOrthographicOffCenter = new OrthographicOffCenterFrustum();
|
||||
|
||||
const scratchColor = new Color();
|
||||
const scratchSplits = [1.0, 100000.0];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DebugCameraPrimitive.prototype.update = function (frameState) {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
const planesPrimitives = this._planesPrimitives;
|
||||
const outlinePrimitives = this._outlinePrimitives;
|
||||
let i;
|
||||
let length;
|
||||
|
||||
if (this._updateOnChange) {
|
||||
// Recreate the primitive every frame
|
||||
length = planesPrimitives.length;
|
||||
for (i = 0; i < length; ++i) {
|
||||
outlinePrimitives[i] =
|
||||
outlinePrimitives[i] && outlinePrimitives[i].destroy();
|
||||
planesPrimitives[i] =
|
||||
planesPrimitives[i] && planesPrimitives[i].destroy();
|
||||
}
|
||||
planesPrimitives.length = 0;
|
||||
outlinePrimitives.length = 0;
|
||||
}
|
||||
|
||||
if (planesPrimitives.length === 0) {
|
||||
const camera = this._camera;
|
||||
const cameraFrustum = camera.frustum;
|
||||
let frustum;
|
||||
if (cameraFrustum instanceof PerspectiveFrustum) {
|
||||
frustum = scratchPerspective;
|
||||
} else if (cameraFrustum instanceof PerspectiveOffCenterFrustum) {
|
||||
frustum = scratchPerspectiveOffCenter;
|
||||
} else if (cameraFrustum instanceof OrthographicFrustum) {
|
||||
frustum = scratchOrthographic;
|
||||
} else {
|
||||
frustum = scratchOrthographicOffCenter;
|
||||
}
|
||||
frustum = cameraFrustum.clone(frustum);
|
||||
|
||||
let numFrustums;
|
||||
let frustumSplits = this._frustumSplits;
|
||||
if (!defined(frustumSplits) || frustumSplits.length <= 1) {
|
||||
// Use near and far planes if no splits created
|
||||
frustumSplits = scratchSplits;
|
||||
frustumSplits[0] = this._camera.frustum.near;
|
||||
frustumSplits[1] = this._camera.frustum.far;
|
||||
numFrustums = 1;
|
||||
} else {
|
||||
numFrustums = frustumSplits.length - 1;
|
||||
}
|
||||
|
||||
const position = camera.positionWC;
|
||||
const direction = camera.directionWC;
|
||||
const up = camera.upWC;
|
||||
let right = camera.rightWC;
|
||||
right = Cartesian3.negate(right, scratchRight);
|
||||
|
||||
const rotation = scratchRotation;
|
||||
Matrix3.setColumn(rotation, 0, right, rotation);
|
||||
Matrix3.setColumn(rotation, 1, up, rotation);
|
||||
Matrix3.setColumn(rotation, 2, direction, rotation);
|
||||
|
||||
const orientation = Quaternion.fromRotationMatrix(
|
||||
rotation,
|
||||
scratchOrientation,
|
||||
);
|
||||
|
||||
planesPrimitives.length = outlinePrimitives.length = numFrustums;
|
||||
|
||||
for (i = 0; i < numFrustums; ++i) {
|
||||
frustum.near = frustumSplits[i];
|
||||
frustum.far = frustumSplits[i + 1];
|
||||
|
||||
planesPrimitives[i] = new Primitive({
|
||||
geometryInstances: new GeometryInstance({
|
||||
geometry: new FrustumGeometry({
|
||||
origin: position,
|
||||
orientation: orientation,
|
||||
frustum: frustum,
|
||||
_drawNearPlane: i === 0,
|
||||
}),
|
||||
attributes: {
|
||||
color: ColorGeometryInstanceAttribute.fromColor(
|
||||
Color.fromAlpha(this._color, 0.1, scratchColor),
|
||||
),
|
||||
},
|
||||
id: this.id,
|
||||
pickPrimitive: this,
|
||||
}),
|
||||
appearance: new PerInstanceColorAppearance({
|
||||
translucent: true,
|
||||
flat: true,
|
||||
}),
|
||||
asynchronous: false,
|
||||
});
|
||||
|
||||
outlinePrimitives[i] = new Primitive({
|
||||
geometryInstances: new GeometryInstance({
|
||||
geometry: new FrustumOutlineGeometry({
|
||||
origin: position,
|
||||
orientation: orientation,
|
||||
frustum: frustum,
|
||||
_drawNearPlane: i === 0,
|
||||
}),
|
||||
attributes: {
|
||||
color: ColorGeometryInstanceAttribute.fromColor(this._color),
|
||||
},
|
||||
id: this.id,
|
||||
pickPrimitive: this,
|
||||
}),
|
||||
appearance: new PerInstanceColorAppearance({
|
||||
translucent: false,
|
||||
flat: true,
|
||||
}),
|
||||
asynchronous: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
length = planesPrimitives.length;
|
||||
for (i = 0; i < length; ++i) {
|
||||
outlinePrimitives[i].update(frameState);
|
||||
planesPrimitives[i].update(frameState);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 DebugCameraPrimitive#destroy
|
||||
*/
|
||||
DebugCameraPrimitive.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
|
||||
* p = p && p.destroy();
|
||||
*
|
||||
* @see DebugCameraPrimitive#isDestroyed
|
||||
*/
|
||||
DebugCameraPrimitive.prototype.destroy = function () {
|
||||
const length = this._planesPrimitives.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this._outlinePrimitives[i] =
|
||||
this._outlinePrimitives[i] && this._outlinePrimitives[i].destroy();
|
||||
this._planesPrimitives[i] =
|
||||
this._planesPrimitives[i] && this._planesPrimitives[i].destroy();
|
||||
}
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default DebugCameraPrimitive;
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
import Color from "../Core/Color.js";
|
||||
import DrawCommand from "../Renderer/DrawCommand.js";
|
||||
import ShaderSource from "../Renderer/ShaderSource.js";
|
||||
import ShaderProgram from "../Renderer/ShaderProgram.js";
|
||||
import defined from "../Core/defined.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function DebugInspector() {
|
||||
this._cachedShowFrustumsShaders = {};
|
||||
}
|
||||
|
||||
function getAttributeLocations(shaderProgram) {
|
||||
const attributeLocations = {};
|
||||
const attributes = shaderProgram.vertexAttributes;
|
||||
for (const a in attributes) {
|
||||
if (attributes.hasOwnProperty(a)) {
|
||||
attributeLocations[a] = attributes[a].index;
|
||||
}
|
||||
}
|
||||
|
||||
return attributeLocations;
|
||||
}
|
||||
|
||||
function createDebugShowFrustumsShaderProgram(scene, shaderProgram) {
|
||||
const context = scene.context;
|
||||
const sp = shaderProgram;
|
||||
const fs = sp.fragmentShaderSource.clone();
|
||||
|
||||
const targets = [];
|
||||
fs.sources = fs.sources.map(function (source) {
|
||||
source = ShaderSource.replaceMain(source, "czm_Debug_main");
|
||||
const re = /out_FragData_(\d+)/g;
|
||||
let match;
|
||||
while ((match = re.exec(source)) !== null) {
|
||||
if (targets.indexOf(match[1]) === -1) {
|
||||
targets.push(match[1]);
|
||||
}
|
||||
}
|
||||
return source;
|
||||
});
|
||||
const length = targets.length;
|
||||
|
||||
let newMain = "";
|
||||
newMain += "uniform vec3 debugShowCommandsColor;\n";
|
||||
newMain += "uniform vec3 debugShowFrustumsColor;\n";
|
||||
newMain += "void main() \n" + "{ \n" + " czm_Debug_main(); \n";
|
||||
|
||||
// set debugShowCommandsColor to Color(1.0, 1.0, 1.0, 1.0) to stop rendering scene.debugShowCommands
|
||||
// set debugShowFrustumsColor to Color(1.0, 1.0, 1.0, 1.0) to stop rendering scene.debugShowFrustums
|
||||
let i;
|
||||
if (length > 0) {
|
||||
for (i = 0; i < length; ++i) {
|
||||
newMain += ` out_FragData_${targets[i]}.rgb *= debugShowCommandsColor;\n`;
|
||||
newMain += ` out_FragData_${targets[i]}.rgb *= debugShowFrustumsColor;\n`;
|
||||
}
|
||||
} else {
|
||||
newMain += " out_FragColor.rgb *= debugShowCommandsColor;\n";
|
||||
newMain += " out_FragColor.rgb *= debugShowFrustumsColor;\n";
|
||||
}
|
||||
newMain += "}";
|
||||
|
||||
fs.sources.push(newMain);
|
||||
|
||||
const attributeLocations = getAttributeLocations(sp);
|
||||
|
||||
return ShaderProgram.fromCache({
|
||||
context: context,
|
||||
vertexShaderSource: sp.vertexShaderSource,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: attributeLocations,
|
||||
});
|
||||
}
|
||||
|
||||
const scratchFrustumColor = new Color();
|
||||
function createDebugShowFrustumsUniformMap(scene, command) {
|
||||
// setup uniform for the shader
|
||||
let debugUniformMap;
|
||||
if (!defined(command.uniformMap)) {
|
||||
debugUniformMap = {};
|
||||
} else {
|
||||
debugUniformMap = command.uniformMap;
|
||||
}
|
||||
|
||||
if (
|
||||
defined(debugUniformMap.debugShowCommandsColor) ||
|
||||
defined(debugUniformMap.debugShowFrustumsColor)
|
||||
) {
|
||||
return debugUniformMap;
|
||||
}
|
||||
|
||||
debugUniformMap.debugShowCommandsColor = function () {
|
||||
if (!scene.debugShowCommands) {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
if (!defined(command._debugColor)) {
|
||||
command._debugColor = Color.fromRandom();
|
||||
}
|
||||
|
||||
return command._debugColor;
|
||||
};
|
||||
|
||||
debugUniformMap.debugShowFrustumsColor = function () {
|
||||
if (!scene.debugShowFrustums) {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
// Support up to three frustums. If a command overlaps all
|
||||
// three, it's code is not changed.
|
||||
scratchFrustumColor.red =
|
||||
command.debugOverlappingFrustums & (1 << 0) ? 1.0 : 0.0;
|
||||
scratchFrustumColor.green =
|
||||
command.debugOverlappingFrustums & (1 << 1) ? 1.0 : 0.0;
|
||||
scratchFrustumColor.blue =
|
||||
command.debugOverlappingFrustums & (1 << 2) ? 1.0 : 0.0;
|
||||
scratchFrustumColor.alpha = 1.0;
|
||||
return scratchFrustumColor;
|
||||
};
|
||||
|
||||
return debugUniformMap;
|
||||
}
|
||||
|
||||
const scratchShowFrustumCommand = new DrawCommand();
|
||||
DebugInspector.prototype.executeDebugShowFrustumsCommand = function (
|
||||
scene,
|
||||
command,
|
||||
passState,
|
||||
) {
|
||||
// create debug command
|
||||
const shaderProgramId = command.shaderProgram.id;
|
||||
let debugShaderProgram = this._cachedShowFrustumsShaders[shaderProgramId];
|
||||
if (!defined(debugShaderProgram)) {
|
||||
debugShaderProgram = createDebugShowFrustumsShaderProgram(
|
||||
scene,
|
||||
command.shaderProgram,
|
||||
);
|
||||
|
||||
this._cachedShowFrustumsShaders[shaderProgramId] = debugShaderProgram;
|
||||
}
|
||||
|
||||
const debugCommand = DrawCommand.shallowClone(
|
||||
command,
|
||||
scratchShowFrustumCommand,
|
||||
);
|
||||
debugCommand.shaderProgram = debugShaderProgram;
|
||||
debugCommand.uniformMap = createDebugShowFrustumsUniformMap(scene, command);
|
||||
debugCommand.execute(scene.context, passState);
|
||||
};
|
||||
export default DebugInspector;
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
import ArcType from "../Core/ArcType.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import Frozen from "../Core/Frozen.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import GeometryInstance from "../Core/GeometryInstance.js";
|
||||
import Matrix4 from "../Core/Matrix4.js";
|
||||
import PolylineGeometry from "../Core/PolylineGeometry.js";
|
||||
import PolylineColorAppearance from "./PolylineColorAppearance.js";
|
||||
import Primitive from "./Primitive.js";
|
||||
|
||||
/**
|
||||
* Draws the axes of a reference frame defined by a matrix that transforms to world
|
||||
* coordinates, i.e., Earth's WGS84 coordinates. The most prominent example is
|
||||
* a primitives <code>modelMatrix</code>.
|
||||
* <p>
|
||||
* The X axis is red; Y is green; and Z is blue.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is for debugging only; it is not optimized for production use.
|
||||
* </p>
|
||||
*
|
||||
* @alias DebugModelMatrixPrimitive
|
||||
* @constructor
|
||||
*
|
||||
* @param {object} [options] Object with the following properties:
|
||||
* @param {number} [options.length=10000000.0] The length of the axes in meters.
|
||||
* @param {number} [options.width=2.0] The width of the axes in pixels.
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
|
||||
* @param {boolean} [options.show=true] Determines if this primitive will be shown.
|
||||
* @param {object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
|
||||
*
|
||||
* @example
|
||||
* primitives.add(new Cesium.DebugModelMatrixPrimitive({
|
||||
* modelMatrix : primitive.modelMatrix, // primitive to debug
|
||||
* length : 100000.0,
|
||||
* width : 10.0
|
||||
* }));
|
||||
*/
|
||||
function DebugModelMatrixPrimitive(options) {
|
||||
options = options ?? Frozen.EMPTY_OBJECT;
|
||||
|
||||
/**
|
||||
* The length of the axes in meters.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 10000000.0
|
||||
*/
|
||||
this.length = options.length ?? 10000000.0;
|
||||
this._length = undefined;
|
||||
|
||||
/**
|
||||
* The width of the axes in pixels.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 2.0
|
||||
*/
|
||||
this.width = options.width ?? 2.0;
|
||||
this._width = undefined;
|
||||
|
||||
/**
|
||||
* Determines if this primitive will be shown.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.show = options.show ?? true;
|
||||
|
||||
/**
|
||||
* The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
|
||||
*
|
||||
* @type {Matrix4}
|
||||
* @default {@link Matrix4.IDENTITY}
|
||||
*/
|
||||
this.modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
|
||||
this._modelMatrix = new Matrix4();
|
||||
|
||||
/**
|
||||
* User-defined value returned when the primitive is picked.
|
||||
*
|
||||
* @type {*}
|
||||
* @default undefined
|
||||
*
|
||||
* @see Scene#pick
|
||||
*/
|
||||
this.id = options.id;
|
||||
this._id = undefined;
|
||||
|
||||
this._primitive = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DebugModelMatrixPrimitive.prototype.update = function (frameState) {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!defined(this._primitive) ||
|
||||
!Matrix4.equals(this._modelMatrix, this.modelMatrix) ||
|
||||
this._length !== this.length ||
|
||||
this._width !== this.width ||
|
||||
this._id !== this.id
|
||||
) {
|
||||
this._modelMatrix = Matrix4.clone(this.modelMatrix, this._modelMatrix);
|
||||
this._length = this.length;
|
||||
this._width = this.width;
|
||||
this._id = this.id;
|
||||
|
||||
if (defined(this._primitive)) {
|
||||
this._primitive.destroy();
|
||||
}
|
||||
|
||||
// Workaround projecting (0, 0, 0)
|
||||
if (
|
||||
this.modelMatrix[12] === 0.0 &&
|
||||
this.modelMatrix[13] === 0.0 &&
|
||||
this.modelMatrix[14] === 0.0
|
||||
) {
|
||||
this.modelMatrix[14] = 0.01;
|
||||
}
|
||||
|
||||
const x = new GeometryInstance({
|
||||
geometry: new PolylineGeometry({
|
||||
positions: [Cartesian3.ZERO, Cartesian3.UNIT_X],
|
||||
width: this.width,
|
||||
vertexFormat: PolylineColorAppearance.VERTEX_FORMAT,
|
||||
colors: [Color.RED, Color.RED],
|
||||
arcType: ArcType.NONE,
|
||||
}),
|
||||
modelMatrix: Matrix4.multiplyByUniformScale(
|
||||
this.modelMatrix,
|
||||
this.length,
|
||||
new Matrix4(),
|
||||
),
|
||||
id: this.id,
|
||||
pickPrimitive: this,
|
||||
});
|
||||
const y = new GeometryInstance({
|
||||
geometry: new PolylineGeometry({
|
||||
positions: [Cartesian3.ZERO, Cartesian3.UNIT_Y],
|
||||
width: this.width,
|
||||
vertexFormat: PolylineColorAppearance.VERTEX_FORMAT,
|
||||
colors: [Color.GREEN, Color.GREEN],
|
||||
arcType: ArcType.NONE,
|
||||
}),
|
||||
modelMatrix: Matrix4.multiplyByUniformScale(
|
||||
this.modelMatrix,
|
||||
this.length,
|
||||
new Matrix4(),
|
||||
),
|
||||
id: this.id,
|
||||
pickPrimitive: this,
|
||||
});
|
||||
const z = new GeometryInstance({
|
||||
geometry: new PolylineGeometry({
|
||||
positions: [Cartesian3.ZERO, Cartesian3.UNIT_Z],
|
||||
width: this.width,
|
||||
vertexFormat: PolylineColorAppearance.VERTEX_FORMAT,
|
||||
colors: [Color.BLUE, Color.BLUE],
|
||||
arcType: ArcType.NONE,
|
||||
}),
|
||||
modelMatrix: Matrix4.multiplyByUniformScale(
|
||||
this.modelMatrix,
|
||||
this.length,
|
||||
new Matrix4(),
|
||||
),
|
||||
id: this.id,
|
||||
pickPrimitive: this,
|
||||
});
|
||||
|
||||
this._primitive = new Primitive({
|
||||
geometryInstances: [x, y, z],
|
||||
appearance: new PolylineColorAppearance(),
|
||||
asynchronous: false,
|
||||
});
|
||||
}
|
||||
|
||||
this._primitive.update(frameState);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 DebugModelMatrixPrimitive#destroy
|
||||
*/
|
||||
DebugModelMatrixPrimitive.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
|
||||
* p = p && p.destroy();
|
||||
*
|
||||
* @see DebugModelMatrixPrimitive#isDestroyed
|
||||
*/
|
||||
DebugModelMatrixPrimitive.prototype.destroy = function () {
|
||||
this._primitive = this._primitive && this._primitive.destroy();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default DebugModelMatrixPrimitive;
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// @ts-check
|
||||
|
||||
import WebGLConstants from "../Core/WebGLConstants.js";
|
||||
|
||||
/**
|
||||
* Determines the function used to compare two depths for the depth test.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const DepthFunction = {
|
||||
/**
|
||||
* The depth test never passes.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
NEVER: WebGLConstants.NEVER,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is less than the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
LESS: WebGLConstants.LESS,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is equal to the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
EQUAL: WebGLConstants.EQUAL,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is less than or equal to the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
LESS_OR_EQUAL: WebGLConstants.LEQUAL,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is greater than the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
GREATER: WebGLConstants.GREATER,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is not equal to the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
NOT_EQUAL: WebGLConstants.NOTEQUAL,
|
||||
|
||||
/**
|
||||
* The depth test passes if the incoming depth is greater than or equal to the stored depth.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
GREATER_OR_EQUAL: WebGLConstants.GEQUAL,
|
||||
|
||||
/**
|
||||
* The depth test always passes.
|
||||
*
|
||||
* @type {number}
|
||||
* @constant
|
||||
*/
|
||||
ALWAYS: WebGLConstants.ALWAYS,
|
||||
};
|
||||
|
||||
Object.freeze(DepthFunction);
|
||||
|
||||
export default DepthFunction;
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
import BoundingSphere from "../Core/BoundingSphere.js";
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import ComponentDatatype from "../Core/ComponentDatatype.js";
|
||||
import defined from "../Core/defined.js";
|
||||
import FeatureDetection from "../Core/FeatureDetection.js";
|
||||
import Geometry from "../Core/Geometry.js";
|
||||
import GeometryAttribute from "../Core/GeometryAttribute.js";
|
||||
import OrthographicFrustum from "../Core/OrthographicFrustum.js";
|
||||
import PrimitiveType from "../Core/PrimitiveType.js";
|
||||
import BufferUsage from "../Renderer/BufferUsage.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 DepthPlaneFS from "../Shaders/DepthPlaneFS.js";
|
||||
import DepthPlaneVS from "../Shaders/DepthPlaneVS.js";
|
||||
import SceneMode from "./SceneMode.js";
|
||||
import Ellipsoid from "../Core/Ellipsoid.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function DepthPlane(depthPlaneEllipsoidOffset) {
|
||||
this._rs = undefined;
|
||||
this._sp = undefined;
|
||||
this._va = undefined;
|
||||
this._command = undefined;
|
||||
this._mode = undefined;
|
||||
this._useLogDepth = false;
|
||||
this._ellipsoidOffset = depthPlaneEllipsoidOffset ?? 0;
|
||||
}
|
||||
|
||||
const depthQuadScratch = FeatureDetection.supportsTypedArrays()
|
||||
? new Float32Array(12)
|
||||
: [];
|
||||
const scratchCartesian1 = new Cartesian3();
|
||||
const scratchCartesian2 = new Cartesian3();
|
||||
const scratchCartesian3 = new Cartesian3();
|
||||
const scratchCartesian4 = new Cartesian3();
|
||||
const scratchCartesian5 = new Cartesian3();
|
||||
|
||||
function computeDepthQuad(ellipsoid, frameState) {
|
||||
const radii = ellipsoid.radii;
|
||||
const camera = frameState.camera;
|
||||
let center, eastOffset, northOffset;
|
||||
|
||||
if (camera.frustum instanceof OrthographicFrustum) {
|
||||
center = Cartesian3.ZERO;
|
||||
eastOffset = camera.rightWC;
|
||||
northOffset = camera.upWC;
|
||||
} else {
|
||||
const p = camera.positionWC;
|
||||
|
||||
// Find the corresponding position in the scaled space of the ellipsoid.
|
||||
const q = Cartesian3.multiplyComponents(
|
||||
ellipsoid.oneOverRadii,
|
||||
p,
|
||||
scratchCartesian1,
|
||||
);
|
||||
|
||||
const qUnit = Cartesian3.normalize(q, scratchCartesian2);
|
||||
|
||||
// Determine the east and north directions at q.
|
||||
const eUnit = Cartesian3.normalize(
|
||||
Cartesian3.cross(Cartesian3.UNIT_Z, q, scratchCartesian3),
|
||||
scratchCartesian3,
|
||||
);
|
||||
const nUnit = Cartesian3.normalize(
|
||||
Cartesian3.cross(qUnit, eUnit, scratchCartesian4),
|
||||
scratchCartesian4,
|
||||
);
|
||||
|
||||
const qMagnitude = Cartesian3.magnitude(q);
|
||||
|
||||
// Determine the radius of the 'limb' of the ellipsoid.
|
||||
const wMagnitude = Math.sqrt(qMagnitude * qMagnitude - 1.0);
|
||||
|
||||
// Compute the center and offsets.
|
||||
center = Cartesian3.multiplyByScalar(
|
||||
qUnit,
|
||||
1.0 / qMagnitude,
|
||||
scratchCartesian1,
|
||||
);
|
||||
const scalar = wMagnitude / qMagnitude;
|
||||
eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian2);
|
||||
northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3);
|
||||
}
|
||||
|
||||
// A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
|
||||
const upperLeft = Cartesian3.add(center, northOffset, scratchCartesian5);
|
||||
Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
|
||||
Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);
|
||||
Cartesian3.pack(upperLeft, depthQuadScratch, 0);
|
||||
|
||||
const lowerLeft = Cartesian3.subtract(center, northOffset, scratchCartesian5);
|
||||
Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
|
||||
Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);
|
||||
Cartesian3.pack(lowerLeft, depthQuadScratch, 3);
|
||||
|
||||
const upperRight = Cartesian3.add(center, northOffset, scratchCartesian5);
|
||||
Cartesian3.add(upperRight, eastOffset, upperRight);
|
||||
Cartesian3.multiplyComponents(radii, upperRight, upperRight);
|
||||
Cartesian3.pack(upperRight, depthQuadScratch, 6);
|
||||
|
||||
const lowerRight = Cartesian3.subtract(
|
||||
center,
|
||||
northOffset,
|
||||
scratchCartesian5,
|
||||
);
|
||||
Cartesian3.add(lowerRight, eastOffset, lowerRight);
|
||||
Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);
|
||||
Cartesian3.pack(lowerRight, depthQuadScratch, 9);
|
||||
|
||||
return depthQuadScratch;
|
||||
}
|
||||
|
||||
DepthPlane.prototype.update = function (frameState) {
|
||||
this._mode = frameState.mode;
|
||||
if (frameState.mode !== SceneMode.SCENE3D) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = frameState.context;
|
||||
|
||||
// Allow offsetting the ellipsoid radius to address rendering artifacts below ellipsoid zero elevation.
|
||||
const radii = frameState.mapProjection.ellipsoid.radii;
|
||||
const ellipsoid = new Ellipsoid(
|
||||
radii.x + this._ellipsoidOffset,
|
||||
radii.y + this._ellipsoidOffset,
|
||||
radii.z + this._ellipsoidOffset,
|
||||
);
|
||||
|
||||
const useLogDepth = frameState.useLogDepth;
|
||||
|
||||
if (!defined(this._command)) {
|
||||
this._rs = RenderState.fromCache({
|
||||
// Write depth, not color
|
||||
cull: {
|
||||
enabled: true,
|
||||
},
|
||||
depthTest: {
|
||||
enabled: true,
|
||||
},
|
||||
colorMask: {
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
},
|
||||
});
|
||||
|
||||
this._command = new DrawCommand({
|
||||
renderState: this._rs,
|
||||
boundingVolume: new BoundingSphere(
|
||||
Cartesian3.ZERO,
|
||||
ellipsoid.maximumRadius,
|
||||
),
|
||||
pass: Pass.OPAQUE,
|
||||
owner: this,
|
||||
});
|
||||
}
|
||||
|
||||
if (!defined(this._sp) || this._useLogDepth !== useLogDepth) {
|
||||
this._useLogDepth = useLogDepth;
|
||||
|
||||
const vs = new ShaderSource({
|
||||
sources: [DepthPlaneVS],
|
||||
});
|
||||
const fs = new ShaderSource({
|
||||
sources: [DepthPlaneFS],
|
||||
});
|
||||
if (useLogDepth) {
|
||||
fs.defines.push("LOG_DEPTH");
|
||||
vs.defines.push("LOG_DEPTH");
|
||||
}
|
||||
|
||||
this._sp = ShaderProgram.replaceCache({
|
||||
shaderProgram: this._sp,
|
||||
context: context,
|
||||
vertexShaderSource: vs,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: {
|
||||
position: 0,
|
||||
},
|
||||
});
|
||||
|
||||
this._command.shaderProgram = this._sp;
|
||||
}
|
||||
|
||||
// update depth plane
|
||||
const depthQuad = computeDepthQuad(ellipsoid, frameState);
|
||||
|
||||
// depth plane
|
||||
if (!defined(this._va)) {
|
||||
const geometry = new Geometry({
|
||||
attributes: {
|
||||
position: new GeometryAttribute({
|
||||
componentDatatype: ComponentDatatype.FLOAT,
|
||||
componentsPerAttribute: 3,
|
||||
values: depthQuad,
|
||||
}),
|
||||
},
|
||||
indices: [0, 1, 2, 2, 1, 3],
|
||||
primitiveType: PrimitiveType.TRIANGLES,
|
||||
});
|
||||
|
||||
this._va = VertexArray.fromGeometry({
|
||||
context: context,
|
||||
geometry: geometry,
|
||||
attributeLocations: {
|
||||
position: 0,
|
||||
},
|
||||
bufferUsage: BufferUsage.DYNAMIC_DRAW,
|
||||
});
|
||||
|
||||
this._command.vertexArray = this._va;
|
||||
} else {
|
||||
this._va.getAttribute(0).vertexBuffer.copyFromArrayView(depthQuad);
|
||||
}
|
||||
};
|
||||
|
||||
DepthPlane.prototype.execute = function (context, passState) {
|
||||
if (this._mode === SceneMode.SCENE3D) {
|
||||
this._command.execute(context, passState);
|
||||
}
|
||||
};
|
||||
|
||||
DepthPlane.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
DepthPlane.prototype.destroy = function () {
|
||||
this._sp = this._sp && this._sp.destroy();
|
||||
this._va = this._va && this._va.destroy();
|
||||
};
|
||||
export default DepthPlane;
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
import { MetadataComponentType } from "@cesium/engine";
|
||||
import defined from "../Core/defined.js";
|
||||
import DrawCommand from "../Renderer/DrawCommand.js";
|
||||
import RenderState from "../Renderer/RenderState.js";
|
||||
import ShaderSource from "../Renderer/ShaderSource.js";
|
||||
import MetadataType from "./MetadataType.js";
|
||||
import MetadataPickingPipelineStage from "./Model/MetadataPickingPipelineStage.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function DerivedCommand() {}
|
||||
|
||||
const fragDepthRegex = /\bgl_FragDepth\b/;
|
||||
const discardRegex = /\bdiscard\b/;
|
||||
|
||||
function getDepthOnlyShaderProgram(context, shaderProgram) {
|
||||
const cachedShader = context.shaderCache.getDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"depthOnly",
|
||||
);
|
||||
if (defined(cachedShader)) {
|
||||
return cachedShader;
|
||||
}
|
||||
|
||||
let fs = shaderProgram.fragmentShaderSource;
|
||||
|
||||
let writesDepthOrDiscards = false;
|
||||
const sources = fs.sources;
|
||||
for (let i = 0; i < sources.length; ++i) {
|
||||
if (fragDepthRegex.test(sources[i]) || discardRegex.test(sources[i])) {
|
||||
writesDepthOrDiscards = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const usesLogDepth = fs.defines.indexOf("LOG_DEPTH") >= 0;
|
||||
|
||||
if (!writesDepthOrDiscards && !usesLogDepth) {
|
||||
const source = `void main()
|
||||
{
|
||||
out_FragColor = vec4(1.0);
|
||||
}
|
||||
`;
|
||||
fs = new ShaderSource({
|
||||
sources: [source],
|
||||
});
|
||||
} else if (!writesDepthOrDiscards && usesLogDepth) {
|
||||
const source = `void main()
|
||||
{
|
||||
out_FragColor = vec4(1.0);
|
||||
czm_writeLogDepth();
|
||||
}
|
||||
`;
|
||||
fs = new ShaderSource({
|
||||
defines: ["LOG_DEPTH"],
|
||||
sources: [source],
|
||||
});
|
||||
}
|
||||
|
||||
return context.shaderCache.createDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"depthOnly",
|
||||
{
|
||||
vertexShaderSource: shaderProgram.vertexShaderSource,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: shaderProgram._attributeLocations,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function getDepthOnlyRenderState(scene, renderState) {
|
||||
const cache = scene._depthOnlyRenderStateCache;
|
||||
|
||||
const cachedDepthOnlyState = cache[renderState.id];
|
||||
if (defined(cachedDepthOnlyState)) {
|
||||
return cachedDepthOnlyState;
|
||||
}
|
||||
|
||||
const rs = RenderState.getState(renderState);
|
||||
rs.depthMask = true;
|
||||
rs.colorMask = {
|
||||
red: false,
|
||||
green: false,
|
||||
blue: false,
|
||||
alpha: false,
|
||||
};
|
||||
|
||||
const depthOnlyState = RenderState.fromCache(rs);
|
||||
cache[renderState.id] = depthOnlyState;
|
||||
|
||||
return depthOnlyState;
|
||||
}
|
||||
|
||||
DerivedCommand.createDepthOnlyDerivedCommand = function (
|
||||
scene,
|
||||
command,
|
||||
context,
|
||||
result,
|
||||
) {
|
||||
// For a depth only pass, we bind a framebuffer with only a depth attachment (no color attachments),
|
||||
// do not write color, and write depth. If the fragment shader doesn't modify the fragment depth
|
||||
// or discard, the driver can replace the fragment shader with a pass-through shader. We're unsure if this
|
||||
// actually happens so we modify the shader to use a pass-through fragment shader.
|
||||
|
||||
if (!defined(result)) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
const shader = result.depthOnlyCommand?.shaderProgram;
|
||||
const renderState = result.depthOnlyCommand?.renderState;
|
||||
|
||||
result.depthOnlyCommand = DrawCommand.shallowClone(
|
||||
command,
|
||||
result.depthOnlyCommand,
|
||||
);
|
||||
|
||||
if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) {
|
||||
result.depthOnlyCommand.shaderProgram = getDepthOnlyShaderProgram(
|
||||
context,
|
||||
command.shaderProgram,
|
||||
);
|
||||
result.depthOnlyCommand.renderState = getDepthOnlyRenderState(
|
||||
scene,
|
||||
command.renderState,
|
||||
);
|
||||
result.shaderProgramId = command.shaderProgram.id;
|
||||
} else {
|
||||
result.depthOnlyCommand.shaderProgram = shader;
|
||||
result.depthOnlyCommand.renderState = renderState;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const writeLogDepthRegex = /\s+czm_writeLogDepth\(/;
|
||||
const vertexlogDepthRegex = /\s+czm_vertexLogDepth\(/;
|
||||
|
||||
function getLogDepthShaderProgram(context, shaderProgram) {
|
||||
const disableLogDepthWrite =
|
||||
shaderProgram.fragmentShaderSource.defines.indexOf("LOG_DEPTH_READ_ONLY") >=
|
||||
0;
|
||||
if (disableLogDepthWrite) {
|
||||
return shaderProgram;
|
||||
}
|
||||
|
||||
const cachedShader = context.shaderCache.getDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"logDepth",
|
||||
);
|
||||
if (defined(cachedShader)) {
|
||||
return cachedShader;
|
||||
}
|
||||
|
||||
const attributeLocations = shaderProgram._attributeLocations;
|
||||
const vs = shaderProgram.vertexShaderSource.clone();
|
||||
const fs = shaderProgram.fragmentShaderSource.clone();
|
||||
|
||||
vs.defines = defined(vs.defines) ? vs.defines.slice(0) : [];
|
||||
vs.defines.push("LOG_DEPTH");
|
||||
fs.defines = defined(fs.defines) ? fs.defines.slice(0) : [];
|
||||
fs.defines.push("LOG_DEPTH");
|
||||
|
||||
let writesLogDepth = false;
|
||||
let sources = vs.sources;
|
||||
for (let i = 0; i < sources.length; ++i) {
|
||||
if (vertexlogDepthRegex.test(sources[i])) {
|
||||
writesLogDepth = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!writesLogDepth) {
|
||||
for (let i = 0; i < sources.length; ++i) {
|
||||
sources[i] = ShaderSource.replaceMain(sources[i], "czm_log_depth_main");
|
||||
}
|
||||
|
||||
const logMain = `
|
||||
|
||||
void main()
|
||||
{
|
||||
czm_log_depth_main();
|
||||
czm_vertexLogDepth();
|
||||
}
|
||||
`;
|
||||
sources.push(logMain);
|
||||
}
|
||||
|
||||
sources = fs.sources;
|
||||
|
||||
writesLogDepth = false;
|
||||
for (let i = 0; i < sources.length; ++i) {
|
||||
if (writeLogDepthRegex.test(sources[i])) {
|
||||
writesLogDepth = true;
|
||||
}
|
||||
}
|
||||
// This define indicates that a log depth value is written by the shader but doesn't use czm_writeLogDepth.
|
||||
if (fs.defines.indexOf("LOG_DEPTH_WRITE") !== -1) {
|
||||
writesLogDepth = true;
|
||||
}
|
||||
|
||||
let logSource = "";
|
||||
|
||||
if (!writesLogDepth) {
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i] = ShaderSource.replaceMain(sources[i], "czm_log_depth_main");
|
||||
}
|
||||
|
||||
logSource = `
|
||||
void main()
|
||||
{
|
||||
czm_log_depth_main();
|
||||
czm_writeLogDepth();
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
sources.push(logSource);
|
||||
|
||||
return context.shaderCache.createDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"logDepth",
|
||||
{
|
||||
vertexShaderSource: vs,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: attributeLocations,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
DerivedCommand.createLogDepthCommand = function (command, context, result) {
|
||||
if (!defined(result)) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
const shader = result.command?.shaderProgram;
|
||||
|
||||
result.command = DrawCommand.shallowClone(command, result.command);
|
||||
|
||||
if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) {
|
||||
result.command.shaderProgram = getLogDepthShaderProgram(
|
||||
context,
|
||||
command.shaderProgram,
|
||||
);
|
||||
result.shaderProgramId = command.shaderProgram.id;
|
||||
} else {
|
||||
result.command.shaderProgram = shader;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function getPickShaderProgram(context, shaderProgram, pickId) {
|
||||
const cachedShader = context.shaderCache.getDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"pick",
|
||||
);
|
||||
if (defined(cachedShader)) {
|
||||
return cachedShader;
|
||||
}
|
||||
|
||||
const attributeLocations = shaderProgram._attributeLocations;
|
||||
const { sources, defines } = shaderProgram.fragmentShaderSource;
|
||||
|
||||
const hasFragData = sources.some((source) => source.includes("out_FragData"));
|
||||
const outputColorVariable = hasFragData ? "out_FragData_0" : "out_FragColor";
|
||||
const newMain = `void main ()
|
||||
{
|
||||
czm_non_pick_main();
|
||||
if (${outputColorVariable}.a == 0.0) {
|
||||
discard;
|
||||
}
|
||||
${outputColorVariable} = ${pickId};
|
||||
} `;
|
||||
|
||||
const length = sources.length;
|
||||
const newSources = new Array(length + 1);
|
||||
for (let i = 0; i < length; ++i) {
|
||||
newSources[i] = ShaderSource.replaceMain(sources[i], "czm_non_pick_main");
|
||||
}
|
||||
newSources[length] = newMain;
|
||||
const fragmentShaderSource = new ShaderSource({
|
||||
sources: newSources,
|
||||
defines: defines,
|
||||
});
|
||||
return context.shaderCache.createDerivedShaderProgram(shaderProgram, "pick", {
|
||||
vertexShaderSource: shaderProgram.vertexShaderSource,
|
||||
fragmentShaderSource: fragmentShaderSource,
|
||||
attributeLocations: attributeLocations,
|
||||
});
|
||||
}
|
||||
|
||||
function getPickRenderState(scene, renderState) {
|
||||
const cache = scene.picking.pickRenderStateCache;
|
||||
const cachedPickState = cache[renderState.id];
|
||||
if (defined(cachedPickState)) {
|
||||
return cachedPickState;
|
||||
}
|
||||
|
||||
const rs = RenderState.getState(renderState);
|
||||
rs.blending.enabled = false;
|
||||
|
||||
// Turns on depth writing for opaque and translucent passes
|
||||
// Overlapping translucent geometry on the globe surface may exhibit z-fighting
|
||||
// during the pick pass which may not match the rendered scene. Once
|
||||
// terrain is on by default and ground primitives are used instead
|
||||
// this will become less of a problem.
|
||||
rs.depthMask = true;
|
||||
|
||||
const pickState = RenderState.fromCache(rs);
|
||||
cache[renderState.id] = pickState;
|
||||
return pickState;
|
||||
}
|
||||
|
||||
DerivedCommand.createPickDerivedCommand = function (
|
||||
scene,
|
||||
command,
|
||||
context,
|
||||
result,
|
||||
) {
|
||||
if (!defined(result)) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
const shader = result.pickCommand?.shaderProgram;
|
||||
const renderState = result.pickCommand?.renderState;
|
||||
|
||||
result.pickCommand = DrawCommand.shallowClone(command, result.pickCommand);
|
||||
|
||||
if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) {
|
||||
result.pickCommand.shaderProgram = getPickShaderProgram(
|
||||
context,
|
||||
command.shaderProgram,
|
||||
command.pickId,
|
||||
);
|
||||
result.pickCommand.renderState = getPickRenderState(
|
||||
scene,
|
||||
command.renderState,
|
||||
);
|
||||
result.shaderProgramId = command.shaderProgram.id;
|
||||
} else {
|
||||
result.pickCommand.shaderProgram = shader;
|
||||
result.pickCommand.renderState = renderState;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the value of the specified 'define' directive identifier
|
||||
* with the given value.
|
||||
*
|
||||
* The given defines are the parts of the define directives that are
|
||||
* stored in the `ShaderSource`. For example, the defines may be
|
||||
* `["EXAMPLE", "EXAMPLE_VALUE 123"]`
|
||||
*
|
||||
* Calling `replaceDefine(defines, "EXAMPLE", 999)` will result in
|
||||
* the defines being
|
||||
* `["EXAMPLE 999", "EXAMPLE_VALUE 123"]`
|
||||
*
|
||||
* @param {string[]} defines The define directive identifiers
|
||||
* @param {string} defineName The name (identifier) of the define directive
|
||||
* @param {any} newDefineValue The new value whose string representation
|
||||
* will become the token string for the define directive
|
||||
* @private
|
||||
*/
|
||||
function replaceDefine(defines, defineName, newDefineValue) {
|
||||
const n = defines.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const define = defines[i];
|
||||
const tokens = define.trimStart().split(/\s+/);
|
||||
if (tokens[0] === defineName) {
|
||||
defines[i] = `${defineName} ${newDefineValue}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component count for the given class property, or
|
||||
* its array length if it is an array.
|
||||
*
|
||||
* This will be
|
||||
* `[1, 2, 3, 4]` for `[SCALAR, VEC2, VEC3, VEC4`] types,
|
||||
* or the array length if it is an array.
|
||||
*
|
||||
* @param {MetadataClassProperty} classProperty The class property
|
||||
* @returns {number} The component count
|
||||
* @private
|
||||
*/
|
||||
function getComponentCount(classProperty) {
|
||||
if (!classProperty.isArray) {
|
||||
return MetadataType.getComponentCount(classProperty.type);
|
||||
}
|
||||
return classProperty.arrayLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shader statement that applies the inverse of the
|
||||
* value transform to the given value, based on the given offset
|
||||
* and scale.
|
||||
*
|
||||
* @param {string} input The input value
|
||||
* @param {string} offset The offset
|
||||
* @param {string} scale The scale
|
||||
* @returns {string} The statement
|
||||
*/
|
||||
function unapplyValueTransform(input, offset, scale) {
|
||||
return `((${input} - float(${offset})) / float(${scale}))`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shader statement that applies the inverse of the
|
||||
* normalization, based on the given component type
|
||||
*
|
||||
* @param {string} input The input value
|
||||
* @param {string} componentType The component type
|
||||
* @returns {string} The statement
|
||||
*/
|
||||
function unnormalize(input, componentType) {
|
||||
const max = MetadataComponentType.getMaximum(componentType);
|
||||
return `(${input}) / float(${max})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shader statement that returns the value of the specified
|
||||
* property, normalized to the range [0, 1].
|
||||
*
|
||||
* @param {MetadataClassProperty} classProperty The class property
|
||||
* @param {object} metadataProperty The metadata property, either
|
||||
* a `PropertyTextureProperty` or a `PropertyAttributeProperty`
|
||||
* @returns {string} The string
|
||||
*/
|
||||
function getSourceValueStringScalar(classProperty, metadataProperty) {
|
||||
let result = `float(value)`;
|
||||
|
||||
// The 'hasValueTransform' indicates whether the property
|
||||
// (or its class property) did define an 'offset' or 'scale'.
|
||||
// Even when they had not been defined in the JSON, they are
|
||||
// defined in the object, with default values.
|
||||
if (metadataProperty.hasValueTransform) {
|
||||
const offset = metadataProperty.offset;
|
||||
const scale = metadataProperty.scale;
|
||||
result = unapplyValueTransform(result, offset, scale);
|
||||
}
|
||||
if (!classProperty.normalized) {
|
||||
result = unnormalize(result, classProperty.componentType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shader statement that returns the value of the specified
|
||||
* component of the given property, normalized to the range [0, 1].
|
||||
*
|
||||
* @param {MetadataClassProperty} classProperty The class property
|
||||
* @param {object} metadataProperty The metadata property, either
|
||||
* a `PropertyTextureProperty` or a `PropertyAttributeProperty`
|
||||
* @param {string} componentName The name, in ["x", "y", "z", "w"]
|
||||
* @returns {string} The string
|
||||
*/
|
||||
function getSourceValueStringComponent(
|
||||
classProperty,
|
||||
metadataProperty,
|
||||
componentName,
|
||||
) {
|
||||
const valueString = `value.${componentName}`;
|
||||
let result = `float(${valueString})`;
|
||||
|
||||
// The 'hasValueTransform' indicates whether the property
|
||||
// (or its class property) did define an 'offset' or 'scale'.
|
||||
// Even when they had not been defined in the JSON, they are
|
||||
// defined in the object, with default values
|
||||
// Note that in the 'PropertyTextureProperty' and the
|
||||
// 'PropertyAttributeProperty', these values are
|
||||
// stored as "object types" (like 'Cartesian2'), whereas
|
||||
// in the 'MetadataClassProperty', they are stored as
|
||||
// "array types", e.g. a `[number, number]`
|
||||
if (metadataProperty.hasValueTransform) {
|
||||
const offset = metadataProperty.offset[componentName];
|
||||
const scale = metadataProperty.scale[componentName];
|
||||
result = unapplyValueTransform(result, offset, scale);
|
||||
}
|
||||
if (!classProperty.normalized) {
|
||||
result = unnormalize(result, classProperty.componentType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `ShaderProgram` from the given input that renders metadata
|
||||
* values into the frame buffer, according to the given picked metadata info.
|
||||
*
|
||||
* This will update the `defines` of the fragment shader of the given shader
|
||||
* program, by setting `METADATA_PICKING_ENABLED`, and updating the
|
||||
* `METADATA_PICKING_VALUE_*` defines so that they reflect the components
|
||||
* of the metadata that should be written into the RGBA (vec4) that
|
||||
* ends up as the 'color' in the frame buffer.
|
||||
*
|
||||
* The RGBA values will eventually be converted back into an actual metadata
|
||||
* value in `Picking.js`, by calling `MetadataPicking.decodeMetadataValues`.
|
||||
*
|
||||
* @param {Context} context The context
|
||||
* @param {ShaderProgram} shaderProgram The shader program
|
||||
* @param {PickedMetadataInfo} pickedMetadataInfo The picked metadata info
|
||||
* @returns {ShaderProgram} The new shader program
|
||||
* @private
|
||||
*/
|
||||
function getPickMetadataShaderProgram(
|
||||
context,
|
||||
shaderProgram,
|
||||
pickedMetadataInfo,
|
||||
) {
|
||||
const schemaId = pickedMetadataInfo.schemaId;
|
||||
const className = pickedMetadataInfo.className;
|
||||
const propertyName = pickedMetadataInfo.propertyName;
|
||||
const keyword = `pickMetadata-${schemaId}-${className}-${propertyName}`;
|
||||
const shader = context.shaderCache.getDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
keyword,
|
||||
);
|
||||
if (defined(shader)) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
const metadataProperty = pickedMetadataInfo.metadataProperty;
|
||||
const classProperty = pickedMetadataInfo.classProperty;
|
||||
const glslType = classProperty.getGlslType();
|
||||
|
||||
// Define the components that will go into the output `metadataValues`.
|
||||
// This will be the 'color' that is written into the frame buffer,
|
||||
// meaning that the values should be in [0.0, 1.0], and will become
|
||||
// values in [0, 255] in the frame buffer.
|
||||
// By default, all of them are 0.0.
|
||||
const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"];
|
||||
const componentCount = getComponentCount(classProperty);
|
||||
if (componentCount === 1) {
|
||||
// When the property is a scalar, store the source value
|
||||
// string directly in `metadataValues.x`
|
||||
sourceValueStrings[0] = getSourceValueStringScalar(
|
||||
classProperty,
|
||||
metadataProperty,
|
||||
);
|
||||
} else {
|
||||
// When the property is an array, store the array elements
|
||||
// in `metadataValues.x/y/z/w`
|
||||
const componentNames = ["x", "y", "z", "w"];
|
||||
for (let i = 0; i < componentCount; i++) {
|
||||
sourceValueStrings[i] = getSourceValueStringComponent(
|
||||
classProperty,
|
||||
metadataProperty,
|
||||
componentNames[i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const newDefines = shaderProgram.fragmentShaderSource.defines.slice();
|
||||
newDefines.push(MetadataPickingPipelineStage.METADATA_PICKING_ENABLED);
|
||||
|
||||
// Replace the defines of the shader, using the type, property
|
||||
// access, and value components that have been determined
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_TYPE,
|
||||
glslType,
|
||||
);
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_STRING,
|
||||
`metadata.${propertyName}`,
|
||||
);
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_X,
|
||||
sourceValueStrings[0],
|
||||
);
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Y,
|
||||
sourceValueStrings[1],
|
||||
);
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_Z,
|
||||
sourceValueStrings[2],
|
||||
);
|
||||
replaceDefine(
|
||||
newDefines,
|
||||
MetadataPickingPipelineStage.METADATA_PICKING_VALUE_COMPONENT_W,
|
||||
sourceValueStrings[3],
|
||||
);
|
||||
|
||||
const newFragmentShaderSource = new ShaderSource({
|
||||
sources: shaderProgram.fragmentShaderSource.sources,
|
||||
defines: newDefines,
|
||||
});
|
||||
const newShader = context.shaderCache.createDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
keyword,
|
||||
{
|
||||
vertexShaderSource: shaderProgram.vertexShaderSource,
|
||||
fragmentShaderSource: newFragmentShaderSource,
|
||||
attributeLocations: shaderProgram._attributeLocations,
|
||||
},
|
||||
);
|
||||
return newShader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DerivedCommand.createPickMetadataDerivedCommand = function (
|
||||
scene,
|
||||
command,
|
||||
context,
|
||||
result,
|
||||
) {
|
||||
if (!defined(result)) {
|
||||
result = {};
|
||||
}
|
||||
result.pickMetadataCommand = DrawCommand.shallowClone(
|
||||
command,
|
||||
result.pickMetadataCommand,
|
||||
);
|
||||
|
||||
result.pickMetadataCommand.shaderProgram = getPickMetadataShaderProgram(
|
||||
context,
|
||||
command.shaderProgram,
|
||||
command.pickedMetadataInfo,
|
||||
);
|
||||
result.pickMetadataCommand.renderState = getPickRenderState(
|
||||
scene,
|
||||
command.renderState,
|
||||
);
|
||||
result.shaderProgramId = command.shaderProgram.id;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function getHdrShaderProgram(context, shaderProgram) {
|
||||
const cachedShader = context.shaderCache.getDerivedShaderProgram(
|
||||
shaderProgram,
|
||||
"HDR",
|
||||
);
|
||||
if (defined(cachedShader)) {
|
||||
return cachedShader;
|
||||
}
|
||||
|
||||
const attributeLocations = shaderProgram._attributeLocations;
|
||||
const vs = shaderProgram.vertexShaderSource.clone();
|
||||
const fs = shaderProgram.fragmentShaderSource.clone();
|
||||
|
||||
vs.defines = defined(vs.defines) ? vs.defines.slice(0) : [];
|
||||
vs.defines.push("HDR");
|
||||
fs.defines = defined(fs.defines) ? fs.defines.slice(0) : [];
|
||||
fs.defines.push("HDR");
|
||||
|
||||
return context.shaderCache.createDerivedShaderProgram(shaderProgram, "HDR", {
|
||||
vertexShaderSource: vs,
|
||||
fragmentShaderSource: fs,
|
||||
attributeLocations: attributeLocations,
|
||||
});
|
||||
}
|
||||
|
||||
DerivedCommand.createHdrCommand = function (command, context, result) {
|
||||
if (!defined(result)) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
const shader = result.command?.shaderProgram;
|
||||
|
||||
result.command = DrawCommand.shallowClone(command, result.command);
|
||||
|
||||
if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) {
|
||||
result.command.shaderProgram = getHdrShaderProgram(
|
||||
context,
|
||||
command.shaderProgram,
|
||||
);
|
||||
result.shaderProgramId = command.shaderProgram.id;
|
||||
} else {
|
||||
result.command.shaderProgram = shader;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
export default DerivedCommand;
|
||||
Generated
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
import defined from "../Core/defined.js";
|
||||
import destroyObject from "../Core/destroyObject.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
import CesiumMath from "../Core/Math.js";
|
||||
import Matrix3 from "../Core/Matrix3.js";
|
||||
import Quaternion from "../Core/Quaternion.js";
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function DeviceOrientationCameraController(scene) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (!defined(scene)) {
|
||||
throw new DeveloperError("scene is required.");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
this._scene = scene;
|
||||
|
||||
this._lastAlpha = undefined;
|
||||
this._lastBeta = undefined;
|
||||
this._lastGamma = undefined;
|
||||
|
||||
this._alpha = undefined;
|
||||
this._beta = undefined;
|
||||
this._gamma = undefined;
|
||||
|
||||
const that = this;
|
||||
|
||||
function callback(e) {
|
||||
const alpha = e.alpha;
|
||||
if (!defined(alpha)) {
|
||||
that._alpha = undefined;
|
||||
that._beta = undefined;
|
||||
that._gamma = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
that._alpha = CesiumMath.toRadians(alpha);
|
||||
that._beta = CesiumMath.toRadians(e.beta);
|
||||
that._gamma = CesiumMath.toRadians(e.gamma);
|
||||
}
|
||||
|
||||
window.addEventListener("deviceorientation", callback, false);
|
||||
|
||||
this._removeListener = function () {
|
||||
window.removeEventListener("deviceorientation", callback, false);
|
||||
};
|
||||
}
|
||||
|
||||
const scratchQuaternion1 = new Quaternion();
|
||||
const scratchQuaternion2 = new Quaternion();
|
||||
const scratchMatrix3 = new Matrix3();
|
||||
|
||||
function rotate(camera, alpha, beta, gamma) {
|
||||
const direction = camera.direction;
|
||||
const right = camera.right;
|
||||
const up = camera.up;
|
||||
|
||||
const bQuat = Quaternion.fromAxisAngle(direction, beta, scratchQuaternion2);
|
||||
const gQuat = Quaternion.fromAxisAngle(right, gamma, scratchQuaternion1);
|
||||
|
||||
const rotQuat = Quaternion.multiply(gQuat, bQuat, gQuat);
|
||||
|
||||
const aQuat = Quaternion.fromAxisAngle(up, alpha, scratchQuaternion2);
|
||||
Quaternion.multiply(aQuat, rotQuat, rotQuat);
|
||||
|
||||
const matrix = Matrix3.fromQuaternion(rotQuat, scratchMatrix3);
|
||||
Matrix3.multiplyByVector(matrix, right, right);
|
||||
Matrix3.multiplyByVector(matrix, up, up);
|
||||
Matrix3.multiplyByVector(matrix, direction, direction);
|
||||
}
|
||||
|
||||
DeviceOrientationCameraController.prototype.update = function () {
|
||||
if (!defined(this._alpha)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined(this._lastAlpha)) {
|
||||
this._lastAlpha = this._alpha;
|
||||
this._lastBeta = this._beta;
|
||||
this._lastGamma = this._gamma;
|
||||
}
|
||||
|
||||
const a = this._lastAlpha - this._alpha;
|
||||
const b = this._lastBeta - this._beta;
|
||||
const g = this._lastGamma - this._gamma;
|
||||
|
||||
rotate(this._scene.camera, -a, b, g);
|
||||
|
||||
this._lastAlpha = this._alpha;
|
||||
this._lastBeta = this._beta;
|
||||
this._lastGamma = this._gamma;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if this object was destroyed; otherwise, false.
|
||||
* <br /><br />
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
||||
*/
|
||||
DeviceOrientationCameraController.prototype.isDestroyed = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the resources held by this object. Destroying an object allows for deterministic
|
||||
* release of 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.
|
||||
*
|
||||
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
||||
*/
|
||||
DeviceOrientationCameraController.prototype.destroy = function () {
|
||||
this._removeListener();
|
||||
return destroyObject(this);
|
||||
};
|
||||
export default DeviceOrientationCameraController;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import Cartesian3 from "../Core/Cartesian3.js";
|
||||
import Check from "../Core/Check.js";
|
||||
import Color from "../Core/Color.js";
|
||||
import DeveloperError from "../Core/DeveloperError.js";
|
||||
|
||||
/**
|
||||
* A light that gets emitted in a single direction from infinitely far away.
|
||||
*
|
||||
* @param {object} options Object with the following properties:
|
||||
* @param {Cartesian3} options.direction The direction in which light gets emitted.
|
||||
* @param {Color} [options.color=Color.WHITE] The color of the light.
|
||||
* @param {number} [options.intensity=1.0] The intensity of the light.
|
||||
*
|
||||
* @exception {DeveloperError} options.direction cannot be zero-length
|
||||
*
|
||||
* @alias DirectionalLight
|
||||
* @constructor
|
||||
*/
|
||||
function DirectionalLight(options) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
Check.typeOf.object("options", options);
|
||||
Check.typeOf.object("options.direction", options.direction);
|
||||
if (Cartesian3.equals(options.direction, Cartesian3.ZERO)) {
|
||||
throw new DeveloperError("options.direction cannot be zero-length");
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
|
||||
/**
|
||||
* The direction in which light gets emitted.
|
||||
* @type {Cartesian3}
|
||||
*/
|
||||
this.direction = Cartesian3.clone(options.direction);
|
||||
|
||||
/**
|
||||
* The color of the light.
|
||||
* @type {Color}
|
||||
* @default Color.WHITE
|
||||
*/
|
||||
this.color = Color.clone(options.color ?? Color.WHITE);
|
||||
|
||||
/**
|
||||
* The intensity of the light.
|
||||
* @type {number}
|
||||
* @default 1.0
|
||||
*/
|
||||
this.intensity = options.intensity ?? 1.0;
|
||||
}
|
||||
|
||||
export default DirectionalLight;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user