Skip to content

Commit

Permalink
Merge pull request #10250 from CesiumGS/model-experimental-clipping-p…
Browse files Browse the repository at this point in the history
…lanes

Add clipping planes to `ModelExperimental`
  • Loading branch information
ptrgags authored and IanLilleyT committed Apr 1, 2022
2 parents e452c92 + 7177228 commit b3b65ec
Show file tree
Hide file tree
Showing 16 changed files with 935 additions and 12 deletions.
2 changes: 0 additions & 2 deletions Apps/Sandcastle/gallery/Terrain Clipping Planes.html
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,6 @@
});
return tileset.readyPromise
.then(function () {
tileset.pointCloudShading.attenuation = true;

// Adjust height so tileset is in terrain
const cartographic = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Refactored metadata API so `tileset.metadata` and `content.group.metadata` are more symmetric with `content.metadata` and `tile.metadata`. [#10224](https://github.com/CesiumGS/cesium/pull/10224)
- Added support for `EXT_structural_metadata` property attributes in `CustomShader` [#10228](https://github.com/CesiumGS/cesium/pull/10228)
- Added partial support for `EXT_structural_metadata` property textures in `CustomShader` [#10247](https://github.com/CesiumGS/cesium/pull/10247)
- Added clipping planes to `ModelExperimental`. [#10250](https://github.com/CesiumGS/cesium/pull/10250)

##### Fixes :wrench:

Expand Down
2 changes: 2 additions & 0 deletions Documentation/Contributors/DocumentationGuide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ function appendForwardSlash(url) {
}
```
To generate documentation for private elements run `npm run generateDocumentation -- --private`.
## Layout Reference
There's a general flow to each documentation block that makes it easy to read. Tags are always in the same order with the same spacing.
Expand Down
2 changes: 1 addition & 1 deletion Source/Scene/ClippingPlaneCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Object.defineProperties(ClippingPlaneCollection.prototype, {
* 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.
* Used for checking if shader regeneration is necessary.
* If this value changes, then shader regeneration is necessary.
*
* @memberof ClippingPlaneCollection.prototype
* @returns {Number} A Number that describes the ClippingPlaneCollection's state.
Expand Down
125 changes: 125 additions & 0 deletions Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import Cartesian2 from "../../Core/Cartesian2.js";
import ClippingPlaneCollection from "../ClippingPlaneCollection.js";
import combine from "../../Core/combine.js";
import Color from "../../Core/Color.js";
import ModelClippingPlanesStageFS from "../../Shaders/ModelExperimental/ModelClippingPlanesStageFS.js";
import ShaderDestination from "../../Renderer/ShaderDestination.js";

/**
* The model clipping planes stage is responsible for applying clipping planes to the model.
*
* @namespace ModelClippingPlanesPipelineStage
*
* @private
*/
const ModelClippingPlanesPipelineStage = {};
ModelClippingPlanesPipelineStage.name = "ModelClippingPlanesPipelineStage"; // Helps with debugging

const textureResolutionScratch = new Cartesian2();
/**
* Process a model. This modifies the following parts of the render resources:
*
* <ul>
* <li>adds a define to the fragment shader to indicate that the model has clipping planes</li>
* <li>adds the defines to the fragment shader for parameters related to clipping planes, such as the number of planes</li>
* <li>adds a function to the fragment shader to apply the clipping planes to the model's base color</li>
* <li>adds the uniforms for the fragment shader for the clipping plane texture and matrix</li>
*</ul>
*
* @param {ModelRenderResources} renderResources The render resources for this model.
* @param {ModelExperimental} model The model.
* @param {FrameState} frameState The frameState.
*
* @private
*/
ModelClippingPlanesPipelineStage.process = function (
renderResources,
model,
frameState
) {
const clippingPlanes = model.clippingPlanes;
const context = frameState.context;
const shaderBuilder = renderResources.shaderBuilder;

shaderBuilder.addDefine(
"HAS_CLIPPING_PLANES",
undefined,
ShaderDestination.FRAGMENT
);

shaderBuilder.addDefine(
"CLIPPING_PLANES_LENGTH",
clippingPlanes.length,
ShaderDestination.FRAGMENT
);

if (clippingPlanes.unionClippingRegions) {
shaderBuilder.addDefine(
"UNION_CLIPPING_REGIONS",
undefined,
ShaderDestination.FRAGMENT
);
}

if (ClippingPlaneCollection.useFloatTexture(context)) {
shaderBuilder.addDefine(
"USE_CLIPPING_PLANES_FLOAT_TEXTURE",
undefined,
ShaderDestination.FRAGMENT
);
}

const textureResolution = ClippingPlaneCollection.getTextureResolution(
clippingPlanes,
context,
textureResolutionScratch
);

shaderBuilder.addDefine(
"CLIPPING_PLANES_TEXTURE_WIDTH",
textureResolution.x,
ShaderDestination.FRAGMENT
);

shaderBuilder.addDefine(
"CLIPPING_PLANES_TEXTURE_HEIGHT",
textureResolution.y,
ShaderDestination.FRAGMENT
);

shaderBuilder.addUniform(
"sampler2D",
"model_clippingPlanes",
ShaderDestination.FRAGMENT
);
shaderBuilder.addUniform(
"vec4",
"model_clippingPlanesEdgeStyle",
ShaderDestination.FRAGMENT
);
shaderBuilder.addUniform(
"mat4",
"model_clippingPlanesMatrix",
ShaderDestination.FRAGMENT
);

shaderBuilder.addFragmentLines([ModelClippingPlanesStageFS]);

const uniformMap = {
model_clippingPlanes: function () {
return clippingPlanes.texture;
},
model_clippingPlanesEdgeStyle: function () {
const style = Color.clone(clippingPlanes.edgeColor);
style.alpha = clippingPlanes.edgeWidth;
return style;
},
model_clippingPlanesMatrix: function () {
return model._clippingPlanesMatrix;
},
};

renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap);
};

export default ModelClippingPlanesPipelineStage;
103 changes: 100 additions & 3 deletions Source/Scene/ModelExperimental/ModelExperimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BoundingSphere from "../../Core/BoundingSphere.js";
import Cartesian3 from "../../Core/Cartesian3.js";
import Check from "../../Core/Check.js";
import ColorBlendMode from "../ColorBlendMode.js";
import ClippingPlaneCollection from "../ClippingPlaneCollection.js";
import defined from "../../Core/defined.js";
import defer from "../../Core/defer.js";
import defaultValue from "../../Core/defaultValue.js";
Expand Down Expand Up @@ -54,6 +55,7 @@ import SplitDirection from "../SplitDirection.js";
* @param {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.
* @param {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.
* @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting.
* @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
* @param {Cartesian3} [options.lightColor] The light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
* @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model.
* @param {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.
Expand Down Expand Up @@ -139,9 +141,9 @@ export default function ModelExperimental(options) {

/**
* If defined, this matrix is used to transform miscellaneous properties like
* image-based lighting instead of the modelMatrix. This is so that when models
* are part of a tileset these properties get transformed relative to common reference
* (such as the root).
* 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
Expand All @@ -158,6 +160,7 @@ export default function ModelExperimental(options) {
this._content = options.content;

this._texturesLoaded = false;
this._defaultTexture = undefined;

const color = options.color;
this._color = defaultValue(color) ? Color.clone(color) : undefined;
Expand Down Expand Up @@ -204,6 +207,17 @@ export default function ModelExperimental(options) {
this._attenuation = pointCloudShading.attenuation;
this._pointCloudShading = pointCloudShading;

// 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

this._lightColor = Cartesian3.clone(options.lightColor);

this._imageBasedLighting = defined(options.imageBasedLighting)
Expand Down Expand Up @@ -765,6 +779,26 @@ Object.defineProperties(ModelExperimental.prototype, {
},
},

/**
* The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
*
* @memberof ModelExperimental.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 light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
* <p>
Expand Down Expand Up @@ -1002,6 +1036,7 @@ ModelExperimental.prototype.resetDrawCommands = function () {

const scratchIBLReferenceFrameMatrix4 = new Matrix4();
const scratchIBLReferenceFrameMatrix3 = new Matrix3();
const scratchClippingPlanesMatrix = new Matrix4();

/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
Expand Down Expand Up @@ -1069,6 +1104,39 @@ ModelExperimental.prototype.update = function (frameState) {
this.resetDrawCommands();
}

// Update the clipping planes collection for this model to detect any changes.
let currentClippingPlanesState = 0;
if (this.isClippingEnabled()) {
if (this._clippingPlanes.owner === this) {
this._clippingPlanes.update(frameState);
}

let clippingPlanesMatrix = scratchClippingPlanesMatrix;
clippingPlanesMatrix = Matrix4.multiply(
context.uniformState.view3D,
referenceMatrix,
clippingPlanesMatrix
);
clippingPlanesMatrix = Matrix4.multiply(
clippingPlanesMatrix,
this._clippingPlanes.modelMatrix,
clippingPlanesMatrix
);
this._clippingPlanesMatrix = Matrix4.inverseTranspose(
clippingPlanesMatrix,
this._clippingPlanesMatrix
);

currentClippingPlanesState = this._clippingPlanes.clippingPlanesState;
}

if (currentClippingPlanesState !== this._clippingPlanesState) {
this.resetDrawCommands();
this._clippingPlanesState = currentClippingPlanesState;
}

this._defaultTexture = context.defaultTexture;

// short-circuit if the model resources aren't ready.
if (!this._resourcesLoaded) {
return;
Expand Down Expand Up @@ -1232,6 +1300,21 @@ function getScale(model, frameState) {
: scale;
}

/**
* Gets whether or not clipping planes are enabled for this model.
*
* @returns {Boolean} <code>true</code> if clipping planes are enabled for this model, <code>false</code>.
* @private
*/
ModelExperimental.prototype.isClippingEnabled = function () {
const clippingPlanes = this._clippingPlanes;
return (
defined(clippingPlanes) &&
clippingPlanes.enabled &&
clippingPlanes.length !== 0
);
};

/**
* Returns true if this object was destroyed; otherwise, false.
* <br /><br />
Expand Down Expand Up @@ -1277,6 +1360,18 @@ ModelExperimental.prototype.destroy = function () {

this.destroyResources();

// Only destroy the ClippingPlaneCollection if this is the owner.
const clippingPlaneCollection = this._clippingPlanes;
if (
defined(clippingPlaneCollection) &&
!clippingPlaneCollection.isDestroyed() &&
clippingPlaneCollection.owner === this
) {
clippingPlaneCollection.destroy();
}
this._clippingPlanes = undefined;

// Only destroy the ImageBasedLighting if this is the owner.
if (
this._shouldDestroyImageBasedLighting &&
!this._imageBasedLighting.isDestroyed()
Expand Down Expand Up @@ -1332,6 +1427,7 @@ ModelExperimental.prototype.destroyResources = function () {
* @param {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.
* @param {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.
* @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and lighting.
* @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
* @param {Cartesian3} [options.lightColor] The light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
* @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model.
* @param {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.
Expand Down Expand Up @@ -1514,6 +1610,7 @@ function makeModelOptions(loader, modelType, options) {
featureIdLabel: options.featureIdLabel,
instanceFeatureIdLabel: options.instanceFeatureIdLabel,
pointCloudShading: options.pointCloudShading,
clippingPlanes: options.clippingPlanes,
lightColor: options.lightColor,
imageBasedLighting: options.imageBasedLighting,
backFaceCulling: options.backFaceCulling,
Expand Down
22 changes: 22 additions & 0 deletions Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,27 @@ ModelExperimental3DTileContent.prototype.update = function (
model.showCreditsOnScreen = tileset.showCreditsOnScreen;
model.splitDirection = tileset.splitDirection;

// Updating clipping planes requires more effort because of ownership checks
const tilesetClippingPlanes = tileset.clippingPlanes;
model.referenceMatrix = tileset.clippingPlanesOriginMatrix;
if (defined(tilesetClippingPlanes) && tile.clippingPlanesDirty) {
// Dereference the clipping planes from the model if they are irrelevant.
model._clippingPlanes =
tilesetClippingPlanes.enabled && tile._isClipped
? tilesetClippingPlanes
: undefined;
}

// If the model references a different ClippingPlaneCollection from the tileset,
// update the model to use the new ClippingPlaneCollection.
if (
defined(tilesetClippingPlanes) &&
defined(model._clippingPlanes) &&
model._clippingPlanes !== tilesetClippingPlanes
) {
model._clippingPlanes = tilesetClippingPlanes;
}

model.update(frameState);
};

Expand Down Expand Up @@ -331,6 +352,7 @@ function makeModelOptions(tileset, tile, content, additionalOptions) {
featureIdLabel: tileset.featureIdLabel,
instanceFeatureIdLabel: tileset.instanceFeatureIdLabel,
pointCloudShading: tileset.pointCloudShading,
clippingPlanes: tileset.clippingPlanes,
backFaceCulling: tileset.backFaceCulling,
shadows: tileset.shadows,
showCreditsOnScreen: tileset.showCreditsOnScreen,
Expand Down
Loading

0 comments on commit b3b65ec

Please sign in to comment.