mirror of https://github.com/CesiumGS/cesium.git
Add height reference behavior to ModelExperimental
This commit is contained in:
parent
b6cf89ee89
commit
d69afa2de3
|
|
@ -8,6 +8,7 @@ import defined from "../../Core/defined.js";
|
|||
import defaultValue from "../../Core/defaultValue.js";
|
||||
import DeveloperError from "../../Core/DeveloperError.js";
|
||||
import GltfLoader from "../GltfLoader.js";
|
||||
import HeightReference from "../HeightReference.js";
|
||||
import ImageBasedLighting from "../ImageBasedLighting.js";
|
||||
import ModelExperimentalAnimationCollection from "./ModelExperimentalAnimationCollection.js";
|
||||
import ModelExperimentalSceneGraph from "./ModelExperimentalSceneGraph.js";
|
||||
|
|
@ -27,6 +28,7 @@ import I3dmLoader from "./I3dmLoader.js";
|
|||
import PntsLoader from "./PntsLoader.js";
|
||||
import Color from "../../Core/Color.js";
|
||||
import SceneMode from "../SceneMode.js";
|
||||
import SceneTransforms from "../SceneTransforms.js";
|
||||
import ShadowMode from "../ShadowMode.js";
|
||||
import SplitDirection from "../SplitDirection.js";
|
||||
|
||||
|
|
@ -42,6 +44,7 @@ import SplitDirection from "../SplitDirection.js";
|
|||
*
|
||||
* @param {Object} options Object with the following properties:
|
||||
* @param {Resource} options.resource The Resource to the 3D model.
|
||||
* @param {Boolean} [options.show=true] Whether or not to render the model.
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
|
||||
* @param {Number} [options.scale=1.0] A uniform scale applied to this model.
|
||||
* @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
|
||||
|
|
@ -55,7 +58,8 @@ import SplitDirection from "../SplitDirection.js";
|
|||
* @param {Boolean} [options.allowPicking=true] When <code>true</code>, each primitive is pickable with {@link Scene#pick}.
|
||||
* @param {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.
|
||||
* @param {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.
|
||||
* @param {Boolean} [options.show=true] Whether or not to render the model.
|
||||
* @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain.
|
||||
* @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
|
||||
* @param {Color} [options.color] A color that blends with the model's rendered color.
|
||||
* @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
|
||||
* @param {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.
|
||||
|
|
@ -212,10 +216,31 @@ export default function ModelExperimental(options) {
|
|||
// Keeps track of resources that need to be destroyed when the Model is destroyed.
|
||||
this._modelResources = [];
|
||||
|
||||
// Computation of the model's bounding sphere and its initial radius is done in ModelExperimentalSceneGraph
|
||||
// Computation of the model's bounding sphere and its initial radius is done
|
||||
// in ModelExperimentalSceneGraph.
|
||||
this._boundingSphere = new BoundingSphere();
|
||||
this._initialRadius = undefined;
|
||||
|
||||
this._heightReference = defaultValue(
|
||||
options.heightReference,
|
||||
HeightReference.NONE
|
||||
);
|
||||
this._heightDirty = this._heightReference !== HeightReference.NONE;
|
||||
this._removeUpdateHeightCallback = undefined;
|
||||
|
||||
this._clampedModelMatrix = undefined; // For use with height reference
|
||||
|
||||
const scene = options.scene;
|
||||
if (defined(scene) && defined(scene.terrainProviderChanged)) {
|
||||
this._terrainProviderChangedCallback = scene.terrainProviderChanged.addEventListener(
|
||||
function () {
|
||||
this._heightDirty = true;
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
this._scene = scene;
|
||||
|
||||
const pointCloudShading = new PointCloudShading(options.pointCloudShading);
|
||||
this._attenuation = pointCloudShading.attenuation;
|
||||
this._pointCloudShading = pointCloudShading;
|
||||
|
|
@ -613,6 +638,28 @@ Object.defineProperties(ModelExperimental.prototype, {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The height reference of the model, which determines how the model is drawn
|
||||
* relative to terrain.
|
||||
*
|
||||
* @memberof ModelExperimental.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;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The structural metadata from the EXT_structural_metadata extension
|
||||
*
|
||||
|
|
@ -1209,12 +1256,32 @@ ModelExperimental.prototype.update = function (frameState) {
|
|||
this._attenuation = this.pointCloudShading.attenuation;
|
||||
}
|
||||
|
||||
const context = frameState.context;
|
||||
const referenceMatrix = defaultValue(this.referenceMatrix, this.modelMatrix);
|
||||
|
||||
// Update the image-based lighting for this model to detect any changes in parameters.
|
||||
// Update the image-based lighting for this model to load any texture uniforms
|
||||
// it uses (specular maps) and to detect any changes in parameters.
|
||||
this._imageBasedLighting.update(frameState);
|
||||
|
||||
const context = frameState.context;
|
||||
this._defaultTexture = context.defaultTexture;
|
||||
|
||||
// Short-circuit if the model resources aren't ready.
|
||||
if (!this._resourcesLoaded || frameState.mode === SceneMode.MORPHING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some of the other features (e.g. image-based lighting, clipping planes)
|
||||
// depend on the model matrix being updated for the current height reference,
|
||||
// so update it first.
|
||||
if (this._heightDirty) {
|
||||
updateClamping(this);
|
||||
this._heightDirty = false;
|
||||
this._updateModelMatrix = true;
|
||||
}
|
||||
|
||||
const modelMatrix = defined(this._clampedModelMatrix)
|
||||
? this._clampedModelMatrix
|
||||
: this.modelMatrix;
|
||||
|
||||
const referenceMatrix = defaultValue(this.referenceMatrix, modelMatrix);
|
||||
if (
|
||||
this._imageBasedLighting.useSphericalHarmonicCoefficients ||
|
||||
this._imageBasedLighting.useSpecularEnvironmentMaps
|
||||
|
|
@ -1241,6 +1308,8 @@ ModelExperimental.prototype.update = function (frameState) {
|
|||
);
|
||||
}
|
||||
|
||||
// This value will have been updated after calling imageBasedLighting.update()
|
||||
// earlier in the function.
|
||||
if (this._imageBasedLighting.shouldRegenerateShaders) {
|
||||
this.resetDrawCommands();
|
||||
}
|
||||
|
|
@ -1276,13 +1345,6 @@ ModelExperimental.prototype.update = function (frameState) {
|
|||
this._clippingPlanesState = currentClippingPlanesState;
|
||||
}
|
||||
|
||||
this._defaultTexture = context.defaultTexture;
|
||||
|
||||
// short-circuit if the model resources aren't ready.
|
||||
if (!this._resourcesLoaded || frameState.mode === SceneMode.MORPHING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameState.mode !== this._sceneMode) {
|
||||
if (this._projectTo2D) {
|
||||
this.resetDrawCommands();
|
||||
|
|
@ -1312,22 +1374,13 @@ ModelExperimental.prototype.update = function (frameState) {
|
|||
this.destroyResources();
|
||||
this._sceneGraph.buildDrawCommands(frameState);
|
||||
this._drawCommandsBuilt = true;
|
||||
|
||||
const model = this;
|
||||
|
||||
if (!model._ready) {
|
||||
model._completeLoad(model, frameState);
|
||||
|
||||
// Don't render until the next frame after the ready promise is resolved
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This is done without a dirty flag so that the model matrix can be updated in-place
|
||||
// without needing to use a setter.
|
||||
if (!Matrix4.equals(this.modelMatrix, this._modelMatrix)) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (frameState.mode !== SceneMode.SCENE3D && this._projectTo2D) {
|
||||
if (frameState.mode === SceneMode.SCENE3D && this._projectTo2D) {
|
||||
throw new DeveloperError(
|
||||
"ModelExperimental.modelMatrix cannot be changed in 2D or Columbus View if projectTo2D is true."
|
||||
);
|
||||
|
|
@ -1335,23 +1388,34 @@ ModelExperimental.prototype.update = function (frameState) {
|
|||
//>>includeEnd('debug');
|
||||
this._updateModelMatrix = true;
|
||||
this._modelMatrix = Matrix4.clone(this.modelMatrix, this._modelMatrix);
|
||||
this._boundingSphere = BoundingSphere.transform(
|
||||
this._sceneGraph.boundingSphere,
|
||||
this.modelMatrix,
|
||||
this._boundingSphere
|
||||
);
|
||||
}
|
||||
|
||||
if (this._updateModelMatrix || this._minimumPixelSize !== 0.0) {
|
||||
this._clampedScale = defined(this._maximumScale)
|
||||
? Math.min(this._scale, this._maximumScale)
|
||||
: this._scale;
|
||||
|
||||
this._boundingSphere = BoundingSphere.transform(
|
||||
this._sceneGraph.boundingSphere,
|
||||
modelMatrix,
|
||||
this._boundingSphere
|
||||
);
|
||||
this._boundingSphere.radius = this._initialRadius * this._clampedScale;
|
||||
this._computedScale = getScale(this, frameState);
|
||||
this._sceneGraph.updateModelMatrix(frameState);
|
||||
this._computedScale = getScale(this, modelMatrix, frameState);
|
||||
this._sceneGraph.updateModelMatrix(modelMatrix, frameState);
|
||||
this._updateModelMatrix = false;
|
||||
}
|
||||
|
||||
// This check occurs after the bounding sphere has been updated so that
|
||||
// to account for the modifications from the clamp-to-ground setting.
|
||||
const model = this;
|
||||
if (!model._ready) {
|
||||
model._completeLoad(model, frameState);
|
||||
|
||||
// Don't render until the next frame after the ready promise is resolved
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._backFaceCullingDirty) {
|
||||
this.sceneGraph.updateBackFaceCulling(this._backFaceCulling);
|
||||
this._backFaceCullingDirty = false;
|
||||
|
|
@ -1416,9 +1480,8 @@ function scaleInPixels(positionWC, radius, frameState) {
|
|||
}
|
||||
|
||||
const scratchPosition = new Cartesian3();
|
||||
const scratchCartographic = new Cartographic();
|
||||
|
||||
function getScale(model, frameState) {
|
||||
function getScale(model, modelMatrix, frameState) {
|
||||
let scale = model.scale;
|
||||
|
||||
if (model.minimumPixelSize !== 0.0 && !model._projectTo2D) {
|
||||
|
|
@ -1428,25 +1491,14 @@ function getScale(model, frameState) {
|
|||
context.drawingBufferWidth,
|
||||
context.drawingBufferHeight
|
||||
);
|
||||
const m = model.modelMatrix;
|
||||
scratchPosition.x = m[12];
|
||||
scratchPosition.y = m[13];
|
||||
scratchPosition.z = m[14];
|
||||
scratchPosition.x = modelMatrix[12];
|
||||
scratchPosition.y = modelMatrix[13];
|
||||
scratchPosition.z = modelMatrix[14];
|
||||
|
||||
if (model._sceneMode !== SceneMode.SCENE3D) {
|
||||
const projection = frameState.mapProjection;
|
||||
const cartographic = projection.ellipsoid.cartesianToCartographic(
|
||||
SceneTransforms.computeActualWgs84Position(
|
||||
frameState,
|
||||
scratchPosition,
|
||||
scratchCartographic
|
||||
);
|
||||
projection.project(cartographic, scratchPosition);
|
||||
|
||||
// In 2D / CV mode, the map is a yz-plane in world space, so the coordinates
|
||||
// need to be reordered accordingly.
|
||||
Cartesian3.fromElements(
|
||||
scratchPosition.z,
|
||||
scratchPosition.x,
|
||||
scratchPosition.y,
|
||||
scratchPosition
|
||||
);
|
||||
}
|
||||
|
|
@ -1474,6 +1526,89 @@ function getScale(model, frameState) {
|
|||
: scale;
|
||||
}
|
||||
|
||||
const scratchCartographic = new Cartographic();
|
||||
|
||||
function getUpdateHeightCallback(model, ellipsoid, cartoPosition) {
|
||||
return function (clampedPosition) {
|
||||
if (model.heightReference === HeightReference.RELATIVE_TO_GROUND) {
|
||||
const clampedCart = ellipsoid.cartesianToCartographic(
|
||||
clampedPosition,
|
||||
scratchCartographic
|
||||
);
|
||||
clampedCart.height += cartoPosition.height;
|
||||
ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
|
||||
}
|
||||
|
||||
const clampedModelMatrix = model._clampedModelMatrix;
|
||||
|
||||
// Modify clamped model matrix to use new height
|
||||
Matrix4.clone(model.modelMatrix, clampedModelMatrix);
|
||||
clampedModelMatrix[12] = clampedPosition.x;
|
||||
clampedModelMatrix[13] = clampedPosition.y;
|
||||
clampedModelMatrix[14] = clampedPosition.z;
|
||||
|
||||
model._heightChanged = true;
|
||||
};
|
||||
}
|
||||
|
||||
function updateClamping(model) {
|
||||
if (defined(model._removeUpdateHeightCallback)) {
|
||||
model._removeUpdateHeightCallback();
|
||||
model._removeUpdateHeightCallback = undefined;
|
||||
}
|
||||
|
||||
const scene = model._scene;
|
||||
if (
|
||||
!defined(scene) ||
|
||||
!defined(scene.globe) ||
|
||||
model.heightReference === HeightReference.NONE
|
||||
) {
|
||||
//>>includeStart('debug', pragmas.debug);
|
||||
if (model.heightReference !== HeightReference.NONE) {
|
||||
throw new DeveloperError(
|
||||
"Height reference is not supported without a scene and globe."
|
||||
);
|
||||
}
|
||||
//>>includeEnd('debug');
|
||||
model._clampedModelMatrix = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const globe = scene.globe;
|
||||
const ellipsoid = globe.ellipsoid;
|
||||
|
||||
// Compute cartographic position so we don't recompute every update
|
||||
const modelMatrix = model.modelMatrix;
|
||||
scratchPosition.x = modelMatrix[12];
|
||||
scratchPosition.y = modelMatrix[13];
|
||||
scratchPosition.z = modelMatrix[14];
|
||||
const cartoPosition = ellipsoid.cartesianToCartographic(scratchPosition);
|
||||
|
||||
if (!defined(model._clampedModelMatrix)) {
|
||||
model._clampedModelMatrix = Matrix4.clone(modelMatrix, new Matrix4());
|
||||
}
|
||||
|
||||
// Install callback to handle updating of terrain tiles
|
||||
const surface = globe._surface;
|
||||
model._removeUpdateHeightCallback = surface.updateHeight(
|
||||
cartoPosition,
|
||||
getUpdateHeightCallback(model, ellipsoid, cartoPosition)
|
||||
);
|
||||
|
||||
// Set the correct height now
|
||||
const height = globe.getHeight(cartoPosition);
|
||||
if (defined(height)) {
|
||||
// Get callback with cartoPosition being the non-clamped position
|
||||
const callback = getUpdateHeightCallback(model, ellipsoid, cartoPosition);
|
||||
|
||||
// Compute the clamped cartesian and call updateHeight callback
|
||||
Cartographic.clone(cartoPosition, scratchCartographic);
|
||||
scratchCartographic.height = height;
|
||||
ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);
|
||||
callback(scratchPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether or not clipping planes are enabled for this model.
|
||||
*
|
||||
|
|
@ -1536,6 +1671,17 @@ ModelExperimental.prototype.destroy = function () {
|
|||
this.destroyResources();
|
||||
this.destroyModelResources();
|
||||
|
||||
// Remove callbacks for height reference behavior.
|
||||
if (defined(this._removeUpdateHeightCallback)) {
|
||||
this._removeUpdateHeightCallback();
|
||||
this._removeUpdateHeightCallback = undefined;
|
||||
}
|
||||
|
||||
if (defined(this._terrainProviderChangedCallback)) {
|
||||
this._terrainProviderChangedCallback();
|
||||
this._terrainProviderChangedCallback = undefined;
|
||||
}
|
||||
|
||||
// Only destroy the ClippingPlaneCollection if this is the owner.
|
||||
const clippingPlaneCollection = this._clippingPlanes;
|
||||
if (
|
||||
|
|
@ -1596,6 +1742,7 @@ ModelExperimental.prototype.destroyModelResources = function () {
|
|||
* @param {Object} options Object with the following properties:
|
||||
* @param {String|Resource} options.url The url to the .gltf or .glb file.
|
||||
* @param {String|Resource} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
|
||||
* @param {Boolean} [options.show=true] Whether or not to render the model.
|
||||
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
|
||||
* @param {Number} [options.scale=1.0] A uniform scale applied to this model.
|
||||
* @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
|
||||
|
|
@ -1612,7 +1759,8 @@ ModelExperimental.prototype.destroyModelResources = function () {
|
|||
* @param {Boolean} [options.allowPicking=true] When <code>true</code>, each primitive is pickable with {@link Scene#pick}.
|
||||
* @param {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.
|
||||
* @param {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.
|
||||
* @param {Boolean} [options.show=true] Whether or not to render the model.
|
||||
* @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain.
|
||||
* @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
|
||||
* @param {Color} [options.color] A color that blends with the model's rendered color.
|
||||
* @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
|
||||
* @param {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.
|
||||
|
|
@ -1808,6 +1956,7 @@ function makeModelOptions(loader, modelType, options) {
|
|||
loader: loader,
|
||||
type: modelType,
|
||||
resource: options.resource,
|
||||
show: options.show,
|
||||
modelMatrix: options.modelMatrix,
|
||||
scale: options.scale,
|
||||
minimumPixelSize: options.minimumPixelSize,
|
||||
|
|
@ -1820,7 +1969,8 @@ function makeModelOptions(loader, modelType, options) {
|
|||
allowPicking: options.allowPicking,
|
||||
customShader: options.customShader,
|
||||
content: options.content,
|
||||
show: options.show,
|
||||
heightReference: options.heightReference,
|
||||
scene: options.scene,
|
||||
color: options.color,
|
||||
colorBlendAmount: options.colorBlendAmount,
|
||||
colorBlendMode: options.colorBlendMode,
|
||||
|
|
|
|||
|
|
@ -221,13 +221,17 @@ function initialize(sceneGraph) {
|
|||
const components = sceneGraph._components;
|
||||
const scene = components.scene;
|
||||
|
||||
computeModelMatrix(sceneGraph);
|
||||
// If the model has a height reference that modifies the model matrix,
|
||||
// it will be accounted for in updateModelMatrix.
|
||||
const modelMatrix = sceneGraph._model.modelMatrix;
|
||||
computeModelMatrix(sceneGraph, modelMatrix);
|
||||
|
||||
const nodes = components.nodes;
|
||||
const nodesLength = nodes.length;
|
||||
|
||||
// Initialize this array to be the same size as the nodes array in the model's file.
|
||||
// This is so nodes can be stored by their index in the file, for future ease of access.
|
||||
// Initialize this array to be the same size as the nodes array in
|
||||
// the model's file. This is so nodes can be stored by their index
|
||||
// in the file, for future ease of access.
|
||||
sceneGraph._runtimeNodes = new Array(nodesLength);
|
||||
|
||||
const rootNodes = scene.nodes;
|
||||
|
|
@ -276,12 +280,12 @@ function initialize(sceneGraph) {
|
|||
}
|
||||
}
|
||||
|
||||
function computeModelMatrix(sceneGraph) {
|
||||
function computeModelMatrix(sceneGraph, modelMatrix) {
|
||||
const components = sceneGraph._components;
|
||||
const model = sceneGraph._model;
|
||||
|
||||
sceneGraph._computedModelMatrix = Matrix4.multiplyTransformation(
|
||||
model.modelMatrix,
|
||||
modelMatrix,
|
||||
components.transform,
|
||||
sceneGraph._computedModelMatrix
|
||||
);
|
||||
|
|
@ -605,9 +609,10 @@ ModelExperimentalSceneGraph.prototype.update = function (
|
|||
};
|
||||
|
||||
ModelExperimentalSceneGraph.prototype.updateModelMatrix = function (
|
||||
modelMatrix,
|
||||
frameState
|
||||
) {
|
||||
computeModelMatrix(this);
|
||||
computeModelMatrix(this, modelMatrix);
|
||||
if (frameState.mode !== SceneMode.SCENE3D) {
|
||||
computeModelMatrix2D(this, frameState);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue