Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clipping planes for models, tilesets, point clouds #5913

Merged
merged 20 commits into from
Nov 2, 2017
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions Apps/Sandcastle/gallery/Clipping Planes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<!DOCTYPE html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an image for the demo.

<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"> <!-- Use Chrome Frame in IE -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta name="description" content="User-defined clipping planes applied to a batched 3D Tileset, point cloud, and model.">
<meta name="cesium-sandcastle-labels" content="Showcases, 3D Tiles">
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script type="text/javascript" src="../../../ThirdParty/requirejs-2.1.20/require.js"></script>
<script type="text/javascript">
require.config({
baseUrl : '../../../Source',
waitSeconds : 60
});
</script>
</head>
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
#toolbar .header {
font-weight: bold;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table><tbody>
<tr>
<td>Type</td>
<td><select data-bind="options: exampleTypes, value: exampleType"></select></td>
</tr>
<tr>
<td>x</td>
<td>
<input type="range" min="-100" max="100" step="1" data-bind="value: xOffset, valueUpdate: 'input'">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset x, y, and z when a new model is selected. After playing around with the BIM tileset I switched to the airplane and couldn't see it because the values were just too big.

It might even be good to limit the range of these values per-model. The sliders are too shifty for the airplane model. Or just scale up the airplane if that's easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So setting the scale on the model causes the clipping planes to behave differently- It will not be relative to the model anymore and move around when the camera changes position. But only when the model is set to a scale other than 1. Do you know why that might be?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scale component of the modelView3D must be shifting the distance portion of the plane in createClippingPlanesFunction. Try removing the scale from the matrix, like: https://gamedev.stackexchange.com/questions/119702/fastest-way-to-neutralize-scale-in-the-transform-matrix

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead I inverted, muliplied by scale, and inverted back. Is that ok?

<input type="text" size="2" data-bind="value: xOffset">
</td>
</tr>
<tr>
<td>y</td>
<td>
<input type="range" min="-100" max="100" step="1" data-bind="value: yOffset, valueUpdate: 'input'">
<input type="text" size="2" data-bind="value: yOffset">
</td>
</tr>
<tr>
<td>z</td>
<td>
<input type="range" min="-100" max="100" step="1" data-bind="value: zOffset, valueUpdate: 'input'">
<input type="text" size="2" data-bind="value: zOffset">
</td>
</tr>
</tbody></table>
</div>

<script id="cesium_sandcastle_script">
function startup(Cesium) {
'use strict';
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer');

var defaultClippingPlanes = [
new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0),
new Cesium.Plane(Cesium.Cartesian3.UNIT_Y, 0),
new Cesium.Plane(Cesium.Cartesian3.UNIT_Z, 0)
];

function loadTileset (url) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout, no space before (.

viewer.entities.removeAll();
viewer.scene.primitives.removeAll();

var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
url : url
}));

tileset.readyPromise.then(function() {
var boundingSphere = tileset.boundingSphere;
viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.2, boundingSphere.radius * 4.0));
viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}).otherwise(function(error) {
throw(error);
});

tileset.clippingPlanes = defaultClippingPlanes;
return tileset.clippingPlanes;
}

function loadModel (url) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an awkward design to have the load functions return the clipping planes array.

In getObservable, perhaps create a new array and pass it to the load function. Basically defaultClippingPlanes becomes a local.

viewer.entities.removeAll();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to call viewer.scene.primitives.removeAll() otherwise tilesets will continue to stay loaded.

To keep is simple, these cleanup lines can go in their own function called from within Cesium.knockout.getObservable(viewModel, 'exampleType').subscribe(

viewer.scene.primitives.removeAll();

var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 1.0);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);

var entity = viewer.entities.add({
name : url,
position : position,
orientation : orientation,
model : {
uri : url
}
});

viewer.trackedEntity = entity;

entity.model.clippingPlanes = defaultClippingPlanes;
return entity.model.clippingPlanes.getValue();
}

// Power Plant design model provided by Bentley Systems
var bimUrl = 'https://beta.cesium.com/api/assets/1459?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNjUyM2I5Yy01YmRhLTQ0MjktOGI0Zi02MDdmYzBjMmY0MjYiLCJpZCI6NDQsImFzc2V0cyI6WzE0NTldLCJpYXQiOjE0OTkyNjQ3ODF9.SW_rwY-ic0TwQBeiweXNqFyywoxnnUBtcVjeCmDGef4';
var pointCloudUrl = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM';
var modelUrl = '../../SampleData/models/CesiumAir/Cesium_Air.glb';

var clippingPlanes = loadTileset(bimUrl);

// Track and create the bindings for the view model

var viewModel = {
exampleType : 'BIM',
exampleTypes : ['BIM', 'Model', 'Point Cloud'],
xOffset : 0.0,
yOffset: 0.0,
zOffset: 0.0
};
var toolbar = document.getElementById('toolbar');
Cesium.knockout.track(viewModel);
Cesium.knockout.applyBindings(viewModel, toolbar);

Cesium.knockout.getObservable(viewModel, 'xOffset').subscribe(function(newValue) {
clippingPlanes[0].distance = parseFloat(newValue);
});

Cesium.knockout.getObservable(viewModel, 'yOffset').subscribe(function(newValue) {
clippingPlanes[1].distance = parseFloat(newValue);
});

Cesium.knockout.getObservable(viewModel, 'zOffset').subscribe(function(newValue) {
clippingPlanes[2].distance = parseFloat(newValue);
});

Cesium.knockout.getObservable(viewModel, 'exampleType').subscribe(
function(newValue) {
if (newValue === 'BIM') {
clippingPlanes = loadTileset(bimUrl);
} else if (newValue === 'Point Cloud') {
clippingPlanes = loadTileset(pointCloudUrl);
} else {
clippingPlanes = loadModel(modelUrl);
}
}
);

//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== "undefined") {
startup(Cesium);
} else if (typeof require === "function") {
require(["Cesium"], startup);
}
</script>
</body>
</html>
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Change Log
* Fixed a 3D Tiles point cloud bug causing a stray point to appear at the center of the screen on certain hardware. [#5599](https://github.com/AnalyticalGraphicsInc/cesium/issues/5599)
* Fixed removing multiple event listeners within event callbacks. [#5827](https://github.com/AnalyticalGraphicsInc/cesium/issues/5827)
* Running `buildApps` now creates a built version of Sandcastle which uses the built version of Cesium for better performance.
* Added `clippingPlanes` property to models and 3D Tilesets, which specify an array of planes to clip the object. [TODO]()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in a new TODO release that we can update before merging into master.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

models and 3D Tilesets

Explicitly name the Cesium classes.

* Fixed a tileset traversal bug when the `skipLevelOfDetail` optimization is off. [#5869](https://github.com/AnalyticalGraphicsInc/cesium/issues/5869)

### 1.37 - 2017-09-01
Expand Down
15 changes: 14 additions & 1 deletion Source/DataSources/ModelGraphics.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ define([
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} that blends with the model's rendered color.
* @param {Property} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] An enum Property specifying how the color blends with the model.
* @param {Property} [options.colorBlendAmount=0.5] A numeric Property specifying 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.
* @param {Property} [options.clippingPlanes=[]] A property specifying an array of {@link Plane} used to clip the model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is currently limited to 6 planes, right? Document that here and throughout.

*
* @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo}
Expand Down Expand Up @@ -91,6 +92,8 @@ define([
this._colorBlendModeSubscription = undefined;
this._colorBlendAmount = undefined;
this._colorBlendAmountSubscription = undefined;
this._clippingPlanes = undefined;
this._clippingPlanesSubscription = undefined;
this._definitionChanged = new Event();

this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
Expand Down Expand Up @@ -242,7 +245,15 @@ define([
* @type {Property}
* @default 0.5
*/
colorBlendAmount : createPropertyDescriptor('colorBlendAmount')
colorBlendAmount : createPropertyDescriptor('colorBlendAmount'),

/**
* A property specifying an array of {@link Plane} used to clip the model.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default []
*/
clippingPlanes: createPropertyDescriptor('clippingPlanes')
});

/**
Expand Down Expand Up @@ -271,6 +282,7 @@ define([
result.color = this.color;
result.colorBlendMode = this.colorBlendMode;
result.colorBlendAmount = this.colorBlendAmount;
result.clippingPlanes = this.clippingPlanes;

return result;
};
Expand Down Expand Up @@ -303,6 +315,7 @@ define([
this.color = defaultValue(this.color, source.color);
this.colorBlendMode = defaultValue(this.colorBlendMode, source.colorBlendMode);
this.colorBlendAmount = defaultValue(this.colorBlendAmount, source.colorBlendAmount);
this.clippingPlanes = defaultValue(this.clippingPlanes, source.clippingPlanes);

var sourceNodeTransformations = source.nodeTransformations;
if (defined(sourceNodeTransformations)) {
Expand Down
2 changes: 2 additions & 0 deletions Source/DataSources/ModelVisualizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ define([
var defaultColor = Color.WHITE;
var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT;
var defaultColorBlendAmount = 0.5;
var defaultClippingPlanes = [];

var modelMatrixScratch = new Matrix4();
var nodeMatrixScratch = new Matrix4();
Expand Down Expand Up @@ -152,6 +153,7 @@ define([
model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, model._color);
model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode);
model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount);
model.clippingPlanes = Property.getValueOrDefault(modelGraphics._clippingPlanes, time, defaultClippingPlanes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to double-check that everything works correctly when two models are added that use different clipping planes. It seems like it should be fine.


if (model.ready) {
var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true);
Expand Down
4 changes: 3 additions & 1 deletion Source/Scene/Batched3DModel3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ define([
pickFragmentShaderLoaded : batchTable.getPickFragmentShaderCallback(),
pickUniformMapLoaded : batchTable.getPickUniformMapCallback(),
addBatchIdToGeneratedShaders : (batchLength > 0), // If the batch table has values in it, generated shaders will need a batchId attribute
pickObject : pickObject
pickObject : pickObject,
clippingPlanes : tileset.clippingPlanes
});
}

Expand Down Expand Up @@ -447,6 +448,7 @@ define([
this._model.modelMatrix = this._tile.computedTransform;
this._model.shadows = this._tileset.shadows;
this._model.debugWireframe = this._tileset.debugWireframe;
this._model.clippingPlanes = this._tileset.clippingPlanes;
this._model.update(frameState);

// If any commands were pushed, add derived commands
Expand Down
9 changes: 9 additions & 0 deletions Source/Scene/Cesium3DTileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ define([
* @param {Number} [options.skipLevels=1] When <code>skipLevelOfDetail</code> is <code>true</code>, a constant defining the minimum number of levels to skip when loading tiles. When it is 0, no levels are skipped. Used in conjunction with <code>skipScreenSpaceErrorFactor</code> to determine which tiles to load.
* @param {Boolean} [options.immediatelyLoadDesiredLevelOfDetail=false] When <code>skipLevelOfDetail</code> is <code>true</code>, only tiles that meet the maximum screen space error will ever be downloaded. Skipping factors are ignored and just the desired tiles are loaded.
* @param {Boolean} [options.loadSiblings=false] When <code>skipLevelOfDetail</code> is <code>true</code>, determines whether siblings of visible tiles are always downloaded during traversal.
* @param {Plane[]} [options.clippingPlanes=[]] An array of {@link Plane} used to clip the tileset.
* @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering.
* @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile.
* @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe.
Expand Down Expand Up @@ -522,6 +523,14 @@ define([
*/
this.loadSiblings = defaultValue(options.loadSiblings, false);

/**
* An array of {@link Plane} used to clip the tileset.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and throughout the reference doc should provide a tad more context: what does it mean to "clip" the tileset?

*
* @type {Plane[]}
* @default []
*/
this.clippingPlanes = defaultValue(options.clippingPlanes, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but here and below we usually would not use defaultValue when the second parameter is an allocation. Instead used defined and ?.

However, couldn't clippingPlanes also just be undefined? I'm pretty sure we allow that in other places where undefined and empty are treated the same.

If not, please make sure that we throw a DeveloperError when it is undefined.


/**
* This property is for debugging only; it is not optimized for production use.
* <p>
Expand Down
57 changes: 55 additions & 2 deletions Source/Scene/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ define([
* @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.
* @param {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.
* @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
* @param {Plane[]} [options.clippingPlanes=[]] An array of {@link Plane} used to clip the model.
*
* @exception {DeveloperError} bgltf is not a valid Binary glTF file.
* @exception {DeveloperError} Only glTF Binary version 1 is supported.
Expand Down Expand Up @@ -576,6 +577,15 @@ define([
*/
this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5);

/**
* An array of {@link Plane} used to clip the model.
*
* @type {Plane[]}
*
* @default []
*/
this.clippingPlanes = defaultValue(options.clippingPlanes, []);

/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
Expand Down Expand Up @@ -2096,9 +2106,10 @@ define([

var premultipliedAlpha = hasPremultipliedAlpha(model);
var blendFS = modifyShaderForColor(fs, premultipliedAlpha);
var clippingFS = modifyShaderForClippingPlanes(blendFS);

var drawVS = modifyShader(vs, id, model._vertexShaderLoaded);
var drawFS = modifyShader(blendFS, id, model._fragmentShaderLoaded);
var drawFS = modifyShader(clippingFS, id, model._fragmentShaderLoaded);

model._rendererResources.programs[id] = ShaderProgram.fromCache({
context : context,
Expand Down Expand Up @@ -3293,6 +3304,32 @@ define([
};
}

function createClippingPlanesLengthFunction(model) {
return function() {
return model.clippingPlanes.length;
};
}

var scratchCartesian = new Cartesian3();
var scratchPlane = new Cartesian4();

function createClippingPlanesFunction(model, context) {
return function() {
var planes = model.clippingPlanes;
var length = planes.length;
var packedPlanes = new Array(length);
for (var i = 0; i < length; ++i) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be limited to 6. Check what happens if you have more than 6 clipping planes, there may be an error in setting the uniform.

var plane = planes[i];
Matrix3.multiplyByVector(context.uniformState.normal, plane.normal, scratchPlane);
Cartesian3.multiplyByScalar(plane.normal, plane.distance, scratchCartesian);
Matrix4.multiplyByPoint(context.uniformState.modelView3D, scratchCartesian, scratchCartesian);
scratchPlane.w = Cartesian3.dot(scratchPlane, scratchCartesian);
packedPlanes[i] = scratchPlane.clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing - instead of allocating the packedPlanes array for each uniform call, store it as a property of Model and create the Cartesian4's in advance, and then clone directly into those. There may need to be some logic in update to handle when the array length changes.

}
return packedPlanes;
};
}

function createColorBlendFunction(model) {
return function() {
return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount);
Expand Down Expand Up @@ -3387,7 +3424,9 @@ define([

uniformMap = combine(uniformMap, {
gltf_color : createColorFunction(model),
gltf_colorBlend : createColorBlendFunction(model)
gltf_colorBlend : createColorBlendFunction(model),
gltf_clippingPlanesLength: createClippingPlanesLengthFunction(model),
gltf_clippingPlanes: createClippingPlanesFunction(model, context)
});

// Allow callback to modify the uniformMap
Expand Down Expand Up @@ -4181,6 +4220,20 @@ define([
}
}

function modifyShaderForClippingPlanes(shader) {
shader = ShaderSource.replaceMain(shader, 'gltf_clip_main');
shader +=
'uniform int gltf_clippingPlanesLength; \n' +
'uniform vec4 gltf_clippingPlanes[czm_maxClippingPlanes]; \n' +
'void main() \n' +
'{ \n' +
' gltf_clip_main(); \n' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clipping planes code should not be run if there are no clipping planes. Put an if in here that checks gltf_numClippingPlanes > 0

' czm_clipPlanes(gltf_clippingPlanes, gltf_clippingPlanesLength); \n' +
'} \n';

return shader;
}

function updateSilhouette(model, frameState) {
// Generate silhouette commands when the silhouette size is greater than 0.0 and the alpha is greater than 0.0
// There are two silhouette commands:
Expand Down
Loading