@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,455 lines (1,321 loc) • 115 kB
JavaScript
import BoundingSphere from "../../Core/BoundingSphere.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 Color from "../../Core/Color.js";
import defined from "../../Core/defined.js";
import Frozen from "../../Core/Frozen.js";
import DeveloperError from "../../Core/DeveloperError.js";
import destroyObject from "../../Core/destroyObject.js";
import DistanceDisplayCondition from "../../Core/DistanceDisplayCondition.js";
import Ellipsoid from "../../Core/Ellipsoid.js";
import Event from "../../Core/Event.js";
import Matrix3 from "../../Core/Matrix3.js";
import Matrix4 from "../../Core/Matrix4.js";
import Resource from "../../Core/Resource.js";
import RuntimeError from "../../Core/RuntimeError.js";
import Pass from "../../Renderer/Pass.js";
import ClippingPlaneCollection from "../ClippingPlaneCollection.js";
import ClippingPolygonCollection from "../ClippingPolygonCollection.js";
import DynamicEnvironmentMapManager from "../DynamicEnvironmentMapManager.js";
import ColorBlendMode from "../ColorBlendMode.js";
import GltfLoader from "../GltfLoader.js";
import HeightReference, {
isHeightReferenceRelative,
} from "../HeightReference.js";
import ImageBasedLighting from "../ImageBasedLighting.js";
import PointCloudShading from "../PointCloudShading.js";
import SceneMode from "../SceneMode.js";
import SceneTransforms from "../SceneTransforms.js";
import ShadowMode from "../ShadowMode.js";
import SplitDirection from "../SplitDirection.js";
import B3dmLoader from "./B3dmLoader.js";
import GeoJsonLoader from "./GeoJsonLoader.js";
import I3dmLoader from "./I3dmLoader.js";
import ModelAnimationCollection from "./ModelAnimationCollection.js";
import ModelFeatureTable from "./ModelFeatureTable.js";
import ModelSceneGraph from "./ModelSceneGraph.js";
import ModelStatistics from "./ModelStatistics.js";
import ModelType from "./ModelType.js";
import ModelUtility from "./ModelUtility.js";
import oneTimeWarning from "../../Core/oneTimeWarning.js";
import PntsLoader from "./PntsLoader.js";
import StyleCommandsNeeded from "./StyleCommandsNeeded.js";
import pickModel from "./pickModel.js";
import ModelImagery from "./ModelImagery.js";
/**
* <div class="notice">
* To construct a Model, call {@link Model.fromGltfAsync}. Do not call the constructor directly.
* </div>
* A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
* <p>
* Cesium supports glTF assets with the following extensions:
* <ul>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/AGI_articulations/README.md|AGI_articulations}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/CESIUM_RTC/README.md|CESIUM_RTC}
* </li>
* <li>
* {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_instance_features|EXT_instance_features}
* </li>
* <li>
* {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features|EXT_mesh_features}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing|EXT_mesh_gpu_instancing}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/pull/2514|EXT_mesh_primitive_restart}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression|EXT_meshopt_compression}
* </li>
* <li>
* {@link https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata|EXT_structural_metadata}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_texture_webp|EXT_texture_webp}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md|KHR_draco_mesh_compression}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_techniques_webgl/README.md|KHR_techniques_webgl}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/main/extensions/1.0/Khronos/KHR_materials_common/README.md|KHR_materials_common}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness|KHR_materials_pbrSpecularGlossiness}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_mesh_quantization|KHR_mesh_quantization}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu|KHR_texture_basisu}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md|KHR_texture_transform}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/blob/main/extensions/1.0/Vendor/WEB3D_quantized_attributes/README.md|WEB3D_quantized_attributes}
* </li>
* <li>
* {@link https://nsgreg.nga.mil/csmwg.jsp|NGA_gpm_local (experimental)}
* </li>
* </ul>
* </p>
* <p>
* Note: for models with compressed textures using the KHR_texture_basisu extension, we recommend power of 2 textures in both dimensions
* for maximum compatibility. This is because some samplers require power of 2 textures ({@link https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL|Using textures in WebGL})
* and KHR_texture_basisu requires multiple of 4 dimensions ({@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_basisu/README.md#additional-requirements|KHR_texture_basisu additional requirements}).
* </p>
*
* @alias Model
* @internalConstructor
*
* @privateParam {object} options Object with the following properties:
* @privateParam {ResourceLoader} options.loader The loader used to load resources for this model.
* @privateParam {ModelType} options.type Type of this model, to distinguish individual glTF files from 3D Tiles internally.
* @privateParam {Resource} options.resource The Resource to the 3D model.
* @privateParam {boolean} [options.show=true] Whether or not to render the model.
* @privateParam {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
* @privateParam {number} [options.scale=1.0] A uniform scale applied to this model.
* @privateParam {boolean} [options.enableVerticalExaggeration=true] If <code>true</code>, the model is exaggerated along the ellipsoid normal when {@link Scene.verticalExaggeration} is set to a value other than <code>1.0</code>.
* @privateParam {number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
* @privateParam {number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
* @privateParam {object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
* @privateParam {boolean} [options.allowPicking=true] When <code>true</code>, each primitive is pickable with {@link Scene#pick}.
* @privateParam {boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified.
* @privateParam {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources.
* @privateParam {boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
* @privateParam {boolean} [options.enableDebugWireframe=false] For debugging only. This must be set to true for debugWireframe to work in WebGL1. This cannot be set after the model has loaded.
* @privateParam {boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe. Will only work for WebGL1 if enableDebugWireframe is set to true.
* @privateParam {boolean} [options.cull=true] Whether or not to cull the model using frustum/horizon culling. If the model is part of a 3D Tiles tileset, this property will always be false, since the 3D Tiles culling system is used.
* @privateParam {boolean} [options.opaquePass=Pass.OPAQUE] The pass to use in the {@link DrawCommand} for the opaque portions of the model.
* @privateParam {CustomShader} [options.customShader] A custom shader. This will add user-defined GLSL code to the vertex and fragment shaders. Using custom shaders with a {@link Cesium3DTileStyle} may lead to undefined behavior.
* @privateParam {Cesium3DTileContent} [options.content] The tile content this model belongs to. This property will be undefined if model is not loaded as part of a tileset.
* @privateParam {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain.
* @privateParam {Scene} [options.scene] Must be passed in for models that use the height reference property.
* @privateParam {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed.
* @privateParam {Color} [options.color] A color that blends with the model's rendered color.
* @privateParam {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
* @privateParam {number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
* @privateParam {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts.
* @privateParam {number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
* @privateParam {boolean} [options.enableShowOutline=true] Whether to enable outlines for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. This can be set to false to avoid the additional processing of geometry at load time. When false, the showOutlines and outlineColor options are ignored.
* @privateParam {boolean} [options.showOutline=true] Whether to display the outline for models using the {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension. When true, outlines are displayed. When false, outlines are not displayed.
* @privateParam {Color} [options.outlineColor=Color.BLACK] The color to use when rendering outlines.
* @privateParam {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
* @privateParam {ClippingPolygonCollection} [options.clippingPolygons] The {@link ClippingPolygonCollection} used to selectively disable rendering the model.
* @privateParam {Cartesian3} [options.lightColor] The light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
* @privateParam {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model.
* @privateParam {DynamicEnvironmentMapManager.ConstructorOptions} [options.environmentMapOptions] The properties for managing dynamic environment maps on this model. Affects lighting.
* @privateParam {boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if the model's color is translucent.
* @privateParam {Credit|string} [options.credit] A credit for the data source, which is displayed on the canvas.
* @privateParam {boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen.
* @privateParam {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this model.
* @privateParam {boolean} [options.projectTo2D=false] Whether to accurately project the model's positions in 2D. If this is true, the model will be projected accurately to 2D, but it will use more memory to do so. If this is false, the model will use less memory and will still render in 2D / CV mode, but its positions may be inaccurate. This disables minimumPixelSize and prevents future modification to the model matrix. This also cannot be set after the model has loaded.
* @privateParam {boolean} [options.enablePick=false] Whether to allow CPU picking with <code>pick</code> when not using WebGL 2 or above. If using WebGL 2 or above, this option will be ignored. If using WebGL 1 and this is true, the <code>pick</code> operation will work correctly, but it will use more memory to do so. If running with WebGL 1 and this is false, the model will use less memory, but <code>pick</code> will always return <code>undefined</code>. This cannot be set after the model has loaded.
* @privateParam {string|number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority.
* @privateParam {string|number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority.
* @privateParam {object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting.
* @privateParam {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this model. This cannot be set after the model has loaded.
*
*
* @see Model.fromGltfAsync
*
* @demo {@link https://sandcastle.cesium.com/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo}
*/
function Model(options) {
options = options ?? Frozen.EMPTY_OBJECT;
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("options.loader", options.loader);
Check.typeOf.object("options.resource", options.resource);
//>>includeEnd('debug');
/**
* The loader used to load resources for this model.
*
* @type {ResourceLoader}
* @private
*/
this._loader = options.loader;
this._resource = options.resource;
/**
* Type of this model, to distinguish individual glTF files from 3D Tiles
* internally.
*
* @type {ModelType}
* @readonly
*
* @private
*/
this.type = options.type ?? ModelType.GLTF;
/**
* The 4x4 transformation matrix that transforms the model from model to world coordinates.
* When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's Cartesian WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
* @example
* const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(options.modelMatrix ?? Matrix4.IDENTITY);
this._modelMatrix = Matrix4.clone(this.modelMatrix);
this._scale = options.scale ?? 1.0;
this._minimumPixelSize = options.minimumPixelSize ?? 0.0;
this._maximumScale = options.maximumScale;
/**
* The scale value after being clamped by the maximum scale parameter.
* Used to adjust bounding spheres without repeated calculation.
*
* @type {number}
* @private
*/
this._clampedScale = defined(this._maximumScale)
? Math.min(this._scale, this._maximumScale)
: this._scale;
this._computedScale = this._clampedScale;
/**
* Whether or not the ModelSceneGraph should call updateModelMatrix.
* This will be true if any of the model matrix, scale, minimum pixel size, or maximum scale are dirty.
*
* @type {number}
* @private
*/
this._updateModelMatrix = false;
/**
* If defined, this matrix is used to transform miscellaneous properties like
* clipping planes and image-based lighting instead of the modelMatrix. This is
* so that when models are part of a tileset, these properties get transformed
* relative to a common reference (such as the root).
*
* @type {Matrix4}
* @private
*/
this.referenceMatrix = undefined;
this._iblReferenceFrameMatrix = Matrix3.clone(Matrix3.IDENTITY); // Derived from reference matrix and the current view matrix
this._resourcesLoaded = false;
this._drawCommandsBuilt = false;
this._ready = false;
this._customShader = options.customShader;
this._content = options.content;
this._texturesLoaded = false;
this._defaultTexture = undefined;
this._activeAnimations = new ModelAnimationCollection(this);
this._clampAnimations = options.clampAnimations ?? true;
// This flag is true when the Cesium API, not a glTF animation, changes
// the transform of a node in the model.
this._userAnimationDirty = false;
this._id = options.id;
this._idDirty = false;
this._color = Color.clone(options.color);
this._colorBlendMode = options.colorBlendMode ?? ColorBlendMode.HIGHLIGHT;
this._colorBlendAmount = options.colorBlendAmount ?? 0.5;
const silhouetteColor = options.silhouetteColor ?? Color.RED;
this._silhouetteColor = Color.clone(silhouetteColor);
this._silhouetteSize = options.silhouetteSize ?? 0.0;
this._silhouetteDirty = false;
// If silhouettes are used for the model, this will be set to the number
// of the stencil buffer used for rendering the silhouette. This is set
// by ModelSilhouettePipelineStage, not by Model itself.
this._silhouetteId = undefined;
this._cull = options.cull ?? true;
this._opaquePass = options.opaquePass ?? Pass.OPAQUE;
this._allowPicking = options.allowPicking ?? true;
this._show = options.show ?? true;
this._style = undefined;
this._styleDirty = false;
this._styleCommandsNeeded = undefined;
let featureIdLabel = options.featureIdLabel ?? "featureId_0";
if (typeof featureIdLabel === "number") {
featureIdLabel = `featureId_${featureIdLabel}`;
}
this._featureIdLabel = featureIdLabel;
let instanceFeatureIdLabel =
options.instanceFeatureIdLabel ?? "instanceFeatureId_0";
if (typeof instanceFeatureIdLabel === "number") {
instanceFeatureIdLabel = `instanceFeatureId_${instanceFeatureIdLabel}`;
}
this._instanceFeatureIdLabel = instanceFeatureIdLabel;
this._featureTables = [];
this._featureTableId = undefined;
this._featureTableIdDirty = true;
// Keeps track of resources that need to be destroyed when the draw commands are reset.
this._pipelineResources = [];
// Keeps track of resources that need to be destroyed when the Model is destroyed.
this._modelResources = [];
// Keeps track of the pick IDs for this model. These are stored and destroyed in the
// pipeline resources array; the purpose of this array is to separate them from other
// resources and update their ID objects when necessary.
this._pickIds = [];
// The model's bounding sphere and its initial radius are computed
// in ModelSceneGraph.
this._boundingSphere = new BoundingSphere();
this._initialRadius = undefined;
this._heightReference = options.heightReference ?? HeightReference.NONE;
this._heightDirty = this._heightReference !== HeightReference.NONE;
this._removeUpdateHeightCallback = undefined;
this._enableVerticalExaggeration = options.enableVerticalExaggeration ?? true;
this._hasVerticalExaggeration = false;
this._clampedModelMatrix = undefined; // For use with height reference
const scene = options.scene;
if (defined(scene) && defined(scene.terrainProviderChanged)) {
this._terrainProviderChangedCallback =
scene.terrainProviderChanged.addEventListener(() => {
this._heightDirty = true;
});
}
this._scene = scene;
this._distanceDisplayCondition = options.distanceDisplayCondition;
const pointCloudShading = new PointCloudShading(options.pointCloudShading);
this._pointCloudShading = pointCloudShading;
this._attenuation = pointCloudShading.attenuation;
this._pointCloudBackFaceCulling = pointCloudShading.backFaceCulling;
// If the given clipping planes don't have an owner, make this model its owner.
// Otherwise, the clipping planes are passed down from a tileset.
const clippingPlanes = options.clippingPlanes;
if (defined(clippingPlanes) && clippingPlanes.owner === undefined) {
ClippingPlaneCollection.setOwner(clippingPlanes, this, "_clippingPlanes");
} else {
this._clippingPlanes = clippingPlanes;
}
this._clippingPlanesState = 0; // If this value changes, the shaders need to be regenerated.
this._clippingPlanesMatrix = Matrix4.clone(Matrix4.IDENTITY); // Derived from reference matrix and the current view matrix
// If the given clipping polygons don't have an owner, make this model its owner.
// Otherwise, the clipping polygons are passed down from a tileset.
const clippingPolygons = options.clippingPolygons;
if (defined(clippingPolygons) && clippingPolygons.owner === undefined) {
ClippingPolygonCollection.setOwner(
clippingPolygons,
this,
"_clippingPolygons",
);
} else {
this._clippingPolygons = clippingPolygons;
}
this._clippingPolygonsState = 0; // If this value changes, the shaders need to be regenerated.
this._modelImagery = new ModelImagery(this);
this._lightColor = Cartesian3.clone(options.lightColor);
this._imageBasedLighting = defined(options.imageBasedLighting)
? options.imageBasedLighting
: new ImageBasedLighting();
this._shouldDestroyImageBasedLighting = !defined(options.imageBasedLighting);
this._environmentMapManager = undefined;
const environmentMapManager = new DynamicEnvironmentMapManager(
options.environmentMapOptions,
);
DynamicEnvironmentMapManager.setOwner(
environmentMapManager,
this,
"_environmentMapManager",
);
this._backFaceCulling = options.backFaceCulling ?? true;
this._backFaceCullingDirty = false;
this._shadows = options.shadows ?? ShadowMode.ENABLED;
this._shadowsDirty = false;
this._debugShowBoundingVolumeDirty = false;
this._debugShowBoundingVolume = options.debugShowBoundingVolume ?? false;
this._enableDebugWireframe = options.enableDebugWireframe ?? false;
this._enableShowOutline = options.enableShowOutline ?? true;
this._debugWireframe = options.debugWireframe ?? false;
// Warning for improper setup of debug wireframe
if (
this._debugWireframe === true &&
this._enableDebugWireframe === false &&
this.type === ModelType.GLTF
) {
oneTimeWarning(
"model-debug-wireframe-ignored",
"enableDebugWireframe must be set to true in Model.fromGltf, otherwise debugWireframe will be ignored.",
);
}
// Credit specified by the user.
let credit = options.credit;
if (typeof credit === "string") {
credit = new Credit(credit);
}
this._credits = [];
this._credit = credit;
// Credits to be added from the Resource (if it is an IonResource)
this._resourceCredits = [];
// Credits parsed from the glTF by GltfLoader.
this._gltfCredits = [];
this._showCreditsOnScreen = options.showCreditsOnScreen ?? false;
this._showCreditsOnScreenDirty = true;
this._splitDirection = options.splitDirection ?? SplitDirection.NONE;
this._enableShowOutline = options.enableShowOutline ?? true;
/**
* Whether to display the outline for models using the
* {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/CESIUM_primitive_outline|CESIUM_primitive_outline} extension.
* When true, outlines are displayed. When false, outlines are not displayed.
*
* @type {boolean}
*
* @default true
*/
this.showOutline = options.showOutline ?? true;
/**
* The color to use when rendering outlines.
*
* @type {Color}
*
* @default Color.BLACK
*/
this.outlineColor = options.outlineColor ?? Color.BLACK;
this._classificationType = options.classificationType;
this._statistics = new ModelStatistics();
this._sceneMode = undefined;
this._projectTo2D = options.projectTo2D ?? false;
this._enablePick = options.enablePick ?? false;
this._fogRenderable = undefined;
this._skipLevelOfDetail = false;
this._ignoreCommands = options.ignoreCommands ?? false;
this._errorEvent = new Event();
this._readyEvent = new Event();
this._texturesReadyEvent = new Event();
this._sceneGraph = undefined;
this._nodesByName = {}; // Stores the nodes by their names in the glTF.
/**
* Used for picking primitives that wrap a model.
*
* @private
*/
this.pickObject = options.pickObject;
}
function handleError(model, error) {
if (model._errorEvent.numberOfListeners > 0) {
model._errorEvent.raiseEvent(error);
return;
}
console.log(error);
}
function createModelFeatureTables(model, structuralMetadata) {
const featureTables = model._featureTables;
const propertyTables = structuralMetadata.propertyTables;
const length = propertyTables.length;
for (let i = 0; i < length; i++) {
const propertyTable = propertyTables[i];
const modelFeatureTable = new ModelFeatureTable({
model: model,
propertyTable: propertyTable,
});
featureTables.push(modelFeatureTable);
}
return featureTables;
}
function selectFeatureTableId(components, model) {
const featureIdLabel = model._featureIdLabel;
const instanceFeatureIdLabel = model._instanceFeatureIdLabel;
let i, j;
let featureIdAttribute;
let node;
// Scan the nodes till we find one with instances, get the feature table ID
// if the feature ID attribute of the user-selected index is present.
for (i = 0; i < components.nodes.length; i++) {
node = components.nodes[i];
if (defined(node.instances)) {
featureIdAttribute = ModelUtility.getFeatureIdsByLabel(
node.instances.featureIds,
instanceFeatureIdLabel,
);
if (
defined(featureIdAttribute) &&
defined(featureIdAttribute.propertyTableId)
) {
return featureIdAttribute.propertyTableId;
}
}
}
// Scan the primitives till we find one with textures or attributes, get the feature table ID
// if the feature ID attribute/texture of the user-selected index is present.
for (i = 0; i < components.nodes.length; i++) {
node = components.nodes[i];
for (j = 0; j < node.primitives.length; j++) {
const primitive = node.primitives[j];
const featureIds = ModelUtility.getFeatureIdsByLabel(
primitive.featureIds,
featureIdLabel,
);
if (defined(featureIds)) {
return featureIds.propertyTableId;
}
}
}
// If there's only one feature table, then select it by default. This is
// to ensure backwards compatibility with the older handling of b3dm models.
if (model._featureTables.length === 1) {
return 0;
}
}
/**
* Returns whether the alpha state has changed between invisible,
* translucent, or opaque.
*
* @private
*/
function isColorAlphaDirty(currentColor, previousColor) {
if (!defined(currentColor) && !defined(previousColor)) {
return false;
}
if (defined(currentColor) !== defined(previousColor)) {
return true;
}
const currentAlpha = currentColor.alpha;
const previousAlpha = previousColor.alpha;
return (
Math.floor(currentAlpha) !== Math.floor(previousAlpha) ||
Math.ceil(currentAlpha) !== Math.ceil(previousAlpha)
);
}
Object.defineProperties(Model.prototype, {
/**
* When <code>true</code>, this model is ready to render, i.e., the external binary, image,
* and shader files were downloaded and the WebGL resources were created.
*
* @memberof Model.prototype
*
* @type {boolean}
* @readonly
*
* @default false
*/
ready: {
get: function () {
return this._ready;
},
},
/**
* Gets an event that is raised when the model encounters an asynchronous rendering 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 ModelError}.
* @memberof Model.prototype
* @type {Event}
* @readonly
*/
errorEvent: {
get: function () {
return this._errorEvent;
},
},
/**
* Gets an event that is raised when the model is loaded and ready for rendering, i.e. when the external resources
* have been downloaded and the WebGL resources are created. Event listeners
* are passed an instance of the {@link Model}.
*
* <p>
* If {@link Model.incrementallyLoadTextures} is true, this event will be raised before all textures are loaded and ready for rendering. Subscribe to {@link Model.texturesReadyEvent} to be notified when the textures are ready.
* </p>
*
* @memberof Model.prototype
* @type {Event}
* @readonly
*/
readyEvent: {
get: function () {
return this._readyEvent;
},
},
/**
* Returns true if textures are loaded separately from the other glTF resources.
*
* @memberof Model.prototype
*
* @type {boolean}
* @readonly
* @private
*/
incrementallyLoadTextures: {
get: function () {
return this._loader.incrementallyLoadTextures ?? false;
},
},
/**
* Gets an event that, if {@link Model.incrementallyLoadTextures} is true, is raised when the model textures are loaded and ready for rendering, i.e. when the external resources
* have been downloaded and the WebGL resources are created. Event listeners
* are passed an instance of the {@link Model}.
*
* @memberof Model.prototype
* @type {Event}
* @readonly
*/
texturesReadyEvent: {
get: function () {
return this._texturesReadyEvent;
},
},
/**
* @private
*/
loader: {
get: function () {
return this._loader;
},
},
/**
* Get the estimated memory usage statistics for this model.
*
* @memberof Model.prototype
*
* @type {ModelStatistics}
* @readonly
*
* @private
*/
statistics: {
get: function () {
return this._statistics;
},
},
/**
* The currently playing glTF animations.
*
* @memberof Model.prototype
*
* @type {ModelAnimationCollection}
* @readonly
*/
activeAnimations: {
get: function () {
return this._activeAnimations;
},
},
/**
* Determines if the model's animations should hold a pose over frames where no keyframes are specified.
*
* @memberof Model.prototype
* @type {boolean}
*
* @default true
*/
clampAnimations: {
get: function () {
return this._clampAnimations;
},
set: function (value) {
this._clampAnimations = value;
},
},
/**
* Whether or not to cull the model using frustum/horizon culling. If the model is part of a 3D Tiles tileset, this property
* will always be false, since the 3D Tiles culling system is used.
*
* @memberof Model.prototype
*
* @type {boolean}
* @readonly
*
* @private
*/
cull: {
get: function () {
return this._cull;
},
},
/**
* The pass to use in the {@link DrawCommand} for the opaque portions of the model.
*
* @memberof Model.prototype
*
* @type {Pass}
* @readonly
*
* @private
*/
opaquePass: {
get: function () {
return this._opaquePass;
},
},
/**
* Point cloud shading settings for controlling point cloud attenuation
* and lighting. For 3D Tiles, this is inherited from the
* {@link Cesium3DTileset}.
*
* @memberof Model.prototype
*
* @type {PointCloudShading}
*/
pointCloudShading: {
get: function () {
return this._pointCloudShading;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
Check.defined("pointCloudShading", value);
//>>includeEnd('debug');
if (value !== this._pointCloudShading) {
this.resetDrawCommands();
}
this._pointCloudShading = value;
},
},
/**
* The model's custom shader, if it exists. Using custom shaders with a {@link Cesium3DTileStyle}
* may lead to undefined behavior.
*
* @memberof Model.prototype
*
* @type {CustomShader}
* @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.
*/
customShader: {
get: function () {
return this._customShader;
},
set: function (value) {
if (value !== this._customShader) {
this.resetDrawCommands();
}
this._customShader = value;
},
},
/**
* The scene graph of this model.
*
* @memberof Model.prototype
*
* @type {ModelSceneGraph}
* @private
*/
sceneGraph: {
get: function () {
return this._sceneGraph;
},
},
/**
* The tile content this model belongs to, if it is loaded as part of a {@link Cesium3DTileset}.
*
* @memberof Model.prototype
*
* @type {Cesium3DTileContent}
* @readonly
*
* @private
*/
content: {
get: function () {
return this._content;
},
},
/**
* The height reference of the model, which determines how the model is drawn
* relative to terrain.
*
* @memberof Model.prototype
*
* @type {HeightReference}
* @default {HeightReference.NONE}
*
*/
heightReference: {
get: function () {
return this._heightReference;
},
set: function (value) {
if (value !== this._heightReference) {
this._heightDirty = true;
}
this._heightReference = value;
},
},
/**
* Gets or sets the distance display condition, which specifies at what distance
* from the camera this model will be displayed.
*
* @memberof Model.prototype
*
* @type {DistanceDisplayCondition}
*
* @default undefined
*
*/
distanceDisplayCondition: {
get: function () {
return this._distanceDisplayCondition;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
if (defined(value) && value.far <= value.near) {
throw new DeveloperError("far must be greater than near");
}
//>>includeEnd('debug');
this._distanceDisplayCondition = DistanceDisplayCondition.clone(
value,
this._distanceDisplayCondition,
);
},
},
/**
* The structural metadata from the EXT_structural_metadata extension
*
* @memberof Model.prototype
*
* @type {StructuralMetadata}
* @readonly
*
* @private
*/
structuralMetadata: {
get: function () {
return this._sceneGraph.components.structuralMetadata;
},
},
/**
* The ID for the feature table to use for picking and styling in this model.
*
* @memberof Model.prototype
*
* @type {number}
*
* @private
*/
featureTableId: {
get: function () {
return this._featureTableId;
},
set: function (value) {
this._featureTableId = value;
},
},
/**
* The feature tables for this model.
*
* @memberof Model.prototype
*
* @type {Array}
* @readonly
*
* @private
*/
featureTables: {
get: function () {
return this._featureTables;
},
set: function (value) {
this._featureTables = value;
},
},
/**
* A user-defined object that is returned when the model is picked.
*
* @memberof Model.prototype
*
* @type {object}
*
* @default undefined
*
* @see Scene#pick
*/
id: {
get: function () {
return this._id;
},
set: function (value) {
if (value !== this._id) {
this._idDirty = true;
}
this._id = value;
},
},
/**
* When <code>true</code>, each primitive is pickable with {@link Scene#pick}. When <code>false</code>, GPU memory is saved.
*
* @memberof Model.prototype
*
* @type {boolean}
* @readonly
*
* @private
*/
allowPicking: {
get: function () {
return this._allowPicking;
},
},
/**
* The style to apply to the features in the model. Cannot be applied if a {@link CustomShader} is also applied.
*
* @memberof Model.prototype
*
* @type {Cesium3DTileStyle}
*/
style: {
get: function () {
return this._style;
},
set: function (value) {
this._style = value;
this._styleDirty = true;
},
},
/**
* The color to blend with the model's rendered color.
*
* @memberof Model.prototype
*
* @type {Color}
*
* @default undefined
*/
color: {
get: function () {
return this._color;
},
set: function (value) {
if (isColorAlphaDirty(value, this._color)) {
this.resetDrawCommands();
}
this._color = Color.clone(value, this._color);
},
},
/**
* Defines how the color blends with the model.
*
* @memberof Model.prototype
*
* @type {Cesium3DTileColorBlendMode|ColorBlendMode}
*
* @default ColorBlendMode.HIGHLIGHT
*/
colorBlendMode: {
get: function () {
return this._colorBlendMode;
},
set: function (value) {
this._colorBlendMode = value;
},
},
/**
* Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
*
* @memberof Model.prototype
*
* @type {number}
*
* @default 0.5
*/
colorBlendAmount: {
get: function () {
return this._colorBlendAmount;
},
set: function (value) {
this._colorBlendAmount = value;
},
},
/**
* The silhouette color.
*
* @memberof Model.prototype
*
* @type {Color}
*
* @default Color.RED
*/
silhouetteColor: {
get: function () {
return this._silhouetteColor;
},
set: function (value) {
if (!Color.equals(value, this._silhouetteColor)) {
const alphaDirty = isColorAlphaDirty(value, this._silhouetteColor);
this._silhouetteDirty = this._silhouetteDirty || alphaDirty;
}
this._silhouetteColor = Color.clone(value, this._silhouetteColor);
},
},
/**
* The size of the silhouette in pixels.
*
* @memberof Model.prototype
*
* @type {number}
*
* @default 0.0
*/
silhouetteSize: {
get: function () {
return this._silhouetteSize;
},
set: function (value) {
if (value !== this._silhouetteSize) {
const currentSize = this._silhouetteSize;
const sizeDirty =
(value > 0.0 && currentSize === 0.0) ||
(value === 0.0 && currentSize > 0.0);
this._silhouetteDirty = this._silhouetteDirty || sizeDirty;
// Back-face culling needs to be updated in case the silhouette size
// is greater than zero.
this._backFaceCullingDirty = this._backFaceCullingDirty || sizeDirty;
}
this._silhouetteSize = value;
},
},
/**
* Gets the model's bounding sphere in world space. This does not take into account
* glTF animations, skins, or morph targets. It also does not account for
* {@link Model#minimumPixelSize}.
*
* @memberof Model.prototype
*
* @type {BoundingSphere}
* @readonly
*/
boundingSphere: {
get: function () {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError(
"The model is not loaded. Use Model.readyEvent or wait for Model.ready to be true.",
);
}
//>>includeEnd('debug');
const modelMatrix = defined(this._clampedModelMatrix)
? this._clampedModelMatrix
: this.modelMatrix;
updateBoundingSphere(this, modelMatrix);
return this._boundingSphere;
},
},
/**
* 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 model.
* </p>
*
* @memberof Model.prototype
*
* @type {boolean}
*
* @default false
*/
debugShowBoundingVolume: {
get: function () {
return this._debugShowBoundingVolume;
},
set: function (value) {
if (this._debugShowBoundingVolume !== value) {
this._debugShowBoundingVolumeDirty = true;
}
this._debugShowBoundingVolume = value;
},
},
/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
* Draws the model in wireframe.
* </p>
*
* @memberof Model.prototype
*
* @type {boolean}
*
* @default false
*/
debugWireframe: {
get: function () {
return this._debugWireframe;
},
set: function (value) {
if (this._debugWireframe !== value) {
this.resetDrawCommands();
}
this._debugWireframe = value;
// Warning for improper setup of debug wireframe
if (
this._debugWireframe === true &&
this._enableDebugWireframe === false &&
this.type === ModelType.GLTF
) {
oneTimeWarning(
"model-debug-wireframe-ignored",
"enableDebugWireframe must be set to true in Model.fromGltfAsync, otherwise debugWireframe will be ignored.",
);
}
},
},
/**
* Whether or not to render the model.
*
* @memberof Model.prototype
*
* @type {boolean}
*
* @default true
*/
show: {
get: function () {
return this._show;
},
set: function (value) {
this._show = value;
},
},
/**
* Label of the feature ID set to use for picking and styling.
* <p>
* For EXT_mesh_features, this is the feature ID's label property, or
* "featureId_N" (where N is the index in the featureIds array) when not
* specified. EXT_feature_metadata did not have a label field, so such
* feature ID sets are always labeled "featureId_N" where N is the index in
* the list of all feature Ids, where feature ID attributes are listed before
* feature ID textures.
* </p>
* <p>
* If featureIdLabel is set to an integer N, it is converted to
* the string "featureId_N" automatically. If both per-primitive and
* per-instance feature IDs are present, the instance feature IDs take
* priority.
* </p>
*
* @memberof Model.prototype
*
* @type {string}
* @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.
*/
featureIdLabel: {
get: function () {
return this._featureIdLabel;
},
set: function (value) {
// indices get converted into featureId_N
if (typeof value === "number") {
value = `featureId_${value}`;
}
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("value", value);
//>>includeEnd('debug');
if (value !== this._featureIdLabel) {
this._featureTableIdDirty = true;
}
this._featureIdLabel = value;
},
},
/**
* Label of the instance feature ID set used for picking and styling.
* <p>
* If instanceFeatureIdLabel is set to an integer N, it is converted to
* the string "instanceFeatureId_N" automatically.
* If both per-primitive and per-instance feature IDs are present, the
* instance feature IDs take priority.
* </p>
*
* @memberof Model.prototype
*
* @type {string}
* @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.
*/
instanceFeatureIdLabel: {
get: function () {
return this._instanceFeatureIdLabel;
},
set: function (value) {
// indices get converted into instanceFeatureId_N
if (typeof value === "number") {
value = `instanceFeatureId_${value}`;
}
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("value", value);
//>>includeEnd('debug');
if (value !== this._instanceFeatureIdLabel) {
this._featureTableIdDirty = true;
}
this._instanceFeatureIdLabel = value;
},
},
/**
* The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
*
* @memberof Model.prototype
*
* @type {ClippingPlaneCollection}
*/
clippingPlanes: {
get: function () {
return this._clippingPlanes;
},
set: function (value) {
if (value !== this._clippingPlanes) {
// Handle destroying old clipping planes, new clipping planes ownership
ClippingPlaneCollection.setOwner(value, this, "_clippingPlanes");
this.resetDrawCommands();
}
},
},
/**
* The {@link ClippingPolygonCollection} used to selectively disable rendering the model.
*
* @memberof Model.prototype
*
* @type {ClippingPolygonCollection}
*/
clippingPolygons: {
get: function () {
return this._clippingPolygons;
},
set: function (value) {
if (value !== this._clippingPolygons) {
// Handle destroying old clipping polygons, new clipping polygons ownership
ClippingPolygonCollection.setOwner(value, this, "_clippingPolygons");
this.resetDrawCommands();
}
},
},
/**
* If <code>true</code>, the model is exaggerated along the ellipsoid normal when {@link Scene.verticalExaggeration} is set to a value other than <code>1.0</code>.
*
* @memberof Model.prototype
* @type {boolean}
* @default true
*
* @example
* // Exaggerate terrain by a factor of 2, but prevent model exaggeration
* scene.verticalExaggeration = 2.0;
* model.enableVerticalExaggeration = false;
*/
enableVerticalExaggeration: {
get: function () {
return this._enableVerticalExaggeration;
},
set: function (value) {
if (value !== this._enableVerticalExaggeration) {
this.resetDrawCommands();
}
this._enableVerticalExaggeration = value;
},
},
/**
* If <code>true</code>, the model is vertically exaggerated along the ellipsoid normal.
*
* @memberof Model.prototype
* @type {boolean}
* @default true
* @readonly
* @private
*/
hasVerticalExaggeration: {
get: function () {
return this._hasVerticalExaggeration;
},
},
/**
* If this model is part of a <code>Model3DTileContent</code> of a tileset,
* then this will return the <code>ImageryLayerCollection</code>
* of that tileset. Otherwise, <code>undefined</code> is returned.
*
* @memberof Model.prototype
* @type {ImageryLayerCollection|undefined}
* @readonly
* @private
*/
imageryLayers: {
get: function () {
if (defined(this._content)) {
const tileset = this._content.tileset;
if (defined(tileset)) {
return tileset.imageryLayers;
}
}
return undefined;
},
},
/**
* The directional light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
* <p>
* Disabling additional light sources by setting
* <code>model.imageBasedLighting.imageBasedLightingFactor = new Cartesian2(0.0, 0.0)</code>
* will make the model much darker. Here, increasing the intensity of the light source will make the model brighter.
* </p>
* @memberof Model.prototype
*
* @type {Cartesian3}
*
* @default undefined
*/
lightColor: {
get: function () {
return this._lightColor;
},
set: function (value) {
if (defined(value) !== defined(this._lightColor)) {
this.resetDrawCommands();
}
this._lightColor = Cartesian3.clone(value, this._lightColor);
},
},
/**
* The properties for managing image-based lighting on this model.
*
* @memberof Model.prototype
*
* @type {ImageBasedLighting}
*/
imageBasedLighting: {
get: function () {
return this._imageBasedLighting;
},
set: function (value) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("imageBasedLighting", value);
//>>includeEnd('debug');
if (value !== this._imageBasedLighting) {
if (
this._shouldDestroyImageBasedLighting &&
!this._imageBasedLighting.isDestroyed()
) {
this._imageBasedLighting.destroy();
}
this._imageBa