-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Fix clipping planes for tiles with RTC and/or no transforms #7034
Fix clipping planes for tiles with RTC and/or no transforms #7034
Conversation
Thanks for the pull request @OmarShehata!
Reviewers, don't forget to make sure that:
I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome. 🌍 🌎 🌏 |
Nice summary of the various cases here. 👍 The approach seems good to me, bump when the code is ready to review.
This case should get caught by the bounding sphere fallback. I don't think there's a clean way for For test data grab any of the test tilesets in Cesium 1.47 or earlier.
Handle the RTC offset for pnts too. Check that cmpt works as well just in case. vctr and geom don't use clipping planes and can be skipped.
Maybe the matrix has to be east-north-up-ified if RTC exists but the transform does not? |
I would also suggest adding to the task list to make the clipping plane documentation super clear with good examples. |
Small update: just confirmed that #6879 is fixed with this PR! And you were right about:
Applying east-north-up fixes it! |
Source/Scene/Cesium3DTile.js
Outdated
|
||
var tileset = this._tileset; | ||
var tileset = this._tileset; // eslint-disable-line no-unreachable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like you are going to take a look at tileset traversal soon, but I wouldn't disable this check so it's not overlooked.
@@ -358,10 +358,16 @@ define([ | |||
content._rtcCenterTransform = Matrix4.clone(Matrix4.IDENTITY); | |||
var rtcCenter = featureTable.getGlobalProperty('RTC_CENTER', ComponentDatatype.FLOAT, 3); | |||
if (defined(rtcCenter)) { | |||
content._rtcCenterTransform = Matrix4.fromTranslation(Cartesian3.fromArray(rtcCenter), content._rtcCenterTransform); | |||
content._rtcCenterTransform = Transforms.eastNorthUpToFixedFrame(Cartesian3.fromArray(rtcCenter), content._rtcCenterTransform); | |||
tile._hasRTC = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and throughout, object properties, even if private, should be defined in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source/Scene/Model.js
Outdated
@@ -4426,7 +4429,12 @@ define([ | |||
var clippingPlanes = this._clippingPlanes; | |||
var currentClippingPlanesState = 0; | |||
if (defined(clippingPlanes) && clippingPlanes.enabled) { | |||
Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); | |||
if (Matrix4.equals(this._clippingPlaneOffsetMatrix, Matrix4.IDENTITY)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a little confusing. When not just initialize it to undefined
?
@@ -465,6 +471,19 @@ define([ | |||
// Update clipping planes | |||
var tilesetClippingPlanes = this._tileset.clippingPlanes; | |||
if (this._tile.clippingPlanesDirty && defined(tilesetClippingPlanes)) { | |||
// Use the root tile's transform. | |||
if (!this._tile._useBoundingSphereForClipping) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does pre-computing this property in initialize
provide much benefit? I don't see it used elsewhere.
If not, I think we should perform the check here to keep similar logic in one place. Same for Instanced3DModel3DTileCotent
.
I think this is finally ready @lilleyse. Thanks @ggetz for the review! The final fix is a lot more simpler and elegant than the original commit. On creation of the root tile, it will check if the transform is missing and save a clippingPlanes matrix that uses the bounding sphere instead. All tile formats will use that if it exists, or otherwise just use the root tile's transform. After finished the traversal update, I realized that #6879 isn't actually a bug. I'm fairly certain that tile that pops up just has an incorrect bounding box, so the traversal code marks it as completely inside so it doesn't attempt to clip it with the shader. This is easier to see when you turn on the debug bounding boxes. |
Update
The note should include a short example of how someone using previous versions can upgrade their clipping plane code. Probably something as simple as flipping some signs, just make sure it is super clear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good @OmarShehata. The approach looks solid and the doc updates are nice and clear. I haven't actually tested anything too carefully yet, though did run through the Sandcastle examples. The only problem I noticed was the i3dm
example in Many Clipping Planes
doesn't seem to work.
this._model._clippingPlaneOffsetMatrix = this._tileset.root.computedTransform; | ||
} else { | ||
this._model._clippingPlaneOffsetMatrix = this._tileset.root._clippingPlaneOffsetMatrix; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be slightly cleaner to move this all to a getter property in Cesium3DTileset
- tileset.clippingPlaneOffsetMatrix
or something like that. A lot of areas can use the getter rather than checking the logic themselves.
E.g. this._model._clippingPlaneOffsetMatrix = this._tileset.clippingPlaneOffsetMatrix
Source/Scene/Cesium3DTile.js
Outdated
if (!defined(this.parent) && !defined(this._header.transform)) { | ||
this._clippingPlaneOffsetMatrix = Transforms.eastNorthUpToFixedFrame(this.boundingSphere.center); | ||
this._useBoundingSphereForClipping = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little wasteful that _useBoundingSphereForClipping
and _clippingPlaneOffsetMatrix
are defined for all Cesium3DTile
s even though it's only used by the root. Maybe they can be moved to properties of Cesium3DTileset
, perhaps initialized right after the json is loaded.
In general we try to keep Cesium3DTile
and other commonly created objects as trimmed down as possible, though the wall of properties in Cesium3DTile
might seem deceiving (and could be improved).
Source/Scene/Cesium3DTile.js
Outdated
@@ -822,6 +828,9 @@ define([ | |||
var clippingPlanes = tileset.clippingPlanes; | |||
if (defined(clippingPlanes) && clippingPlanes.enabled) { | |||
var tileTransform = tileset.root.computedTransform; | |||
if(defined(tileset.root._clippingPlaneOffsetMatrix)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: add space after the if
: if (
Source/Scene/Cesium3DTile.js
Outdated
@@ -857,6 +866,9 @@ define([ | |||
var clippingPlanes = tileset.clippingPlanes; | |||
if (defined(clippingPlanes) && clippingPlanes.enabled) { | |||
var tileTransform = tileset.root.computedTransform; | |||
if(defined(tileset.root._clippingPlaneOffsetMatrix)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same style comment.
* var entity = viewer.entities.add({ | ||
* position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 10000), | ||
* model : { | ||
* uri : '../../../../Apps/SampleData/models/CesiumAir/Cesium_Air.glb', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type of path only works in the built version hosted on the website. Check if other examples do this. It might also be fine to say "model.glb" or something easier to parse.
Source/Scene/Model.js
Outdated
// If defined, it's used to position the clipping planes instead of the modelMatrix. | ||
// This is so that when models are part of a tileset they all get clipped relative | ||
// to the root tile. | ||
this._clippingPlaneOffsetMatrix = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slight wording tweak // If defined, use this matrix to position the clipping planes instead of the modelMatrix.
Source/Scene/PointCloud.js
Outdated
@@ -185,6 +185,10 @@ define([ | |||
this.clippingPlanes = undefined; | |||
this.isClipped = false; | |||
this.clippingPlanesDirty = false; | |||
// If defined, it's used to position the clipping planes instead of the modelMatrix. | |||
// This is so that when point clouds are part of a tileset they all get clipped relative | |||
// to the root tile. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here.
Source/Scene/PointCloud.js
Outdated
} else { | ||
Matrix4.multiply(context.uniformState.view3D, pointCloud._clippingPlaneOffsetMatrix, scratchClippingPlaneMatrix); | ||
} | ||
return scratchClippingPlaneMatrix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is clippingPlanes.modelMatrix
getting accounted for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may explain the failing test: Scene/TimeDynamicPointCloud sets clipping planes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's my bad! That shouldn't have been removed. Thanks for catching this.
* </p> | ||
* <p> | ||
* For 3D Tiles, the root tile's transform is used to position the clipping planes. If a transform is not defined, the root tile's {@link Cesium3DTile#boundingSphere} is used instead. | ||
* </p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add a similar note explaining what the clipping plane's coordinate system is when using it on a model and on the globe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this is necessary? I was thinking the comment above that "clipping planes' coordinates are relative to the object they're attached to" covers the globe and individual models, since it would just be relative to the center of the globe and the model respectively. Since 3D Tiles are a bit more ambiguous I think it needs the extra explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright makes sense.
loadTileset(pointCloudUrl).then(function(tileset) { | ||
tileset.clippingPlanes.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(tileset.boundingSphere.center); | ||
}); | ||
loadTileset(pointCloudUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point cloud example might be a good use case showing when to use the clipping plane's model matrix since the clipping plane is visually off-center from the church due to the offset bounding sphere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried doing this but one issue is that, you only really need to offset the entity, so it wouldn't really need to use the clipping planes' model matrix.
The other issue is that it requires converting back and forth from Cartographic to Cartesian and it kind of makes creating a clipping plane look complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense too. And I remember now that the terrain example uses a model matrix, so one example is enough to illustrate the point.
Should be good now @lilleyse . The i3dm in "Many Clipping Planes" wasn't working because it was still manually setting the clipping plane's model matrix. I updated it to fix it. The only thing I'm unsure about now is that after moving the |
Source/Scene/PointCloud.js
Outdated
Matrix4.multiply(context.uniformState.view3D, pointCloud._modelMatrix, scratchClippingPlaneMatrix); | ||
} else { | ||
Matrix4.multiply(context.uniformState.view3D, pointCloud._clippingPlaneOffsetMatrix, scratchClippingPlaneMatrix); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use defaultValue
here as well.
@@ -297,6 +297,8 @@ define([ | |||
var styleDirty = this._styleDirty; | |||
this._styleDirty = false; | |||
|
|||
pointCloud._clippingPlaneOffsetMatrix = tileset.clippingPlaneOffsetMatrix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably best to rename _clippingPlaneOffsetMatrix
to clippingPlaneOffsetMatrix
like the other variables that are set in here. Consider doing this for Model
too.
In general any variable that can be set from another file doesn't use underscores.
Moving it to the tileset update function sounds fine to me, one extra |
Should be good now @lilleyse ! |
@lilleyse I updated I think the correction to the signed distance happened in this PR #6073. The reason the This Sandcastle localhost:8080/... allows you to move the Here's this same Sandcastle on the current release cesiumjs.org/..., aside from the fact that one of the tile's doesn't use the correct transform, the behavior is the same. It's clipping the same side. |
That's really good to hear that All the doc edits looks good. On my final sweep of the Sandcastle demos I actually have two, hopefully small, suggestions.
|
@lilleyse the reason the color wasn't working was because it was incorrectly computing the distance to the planes in the union function. For the intersection we compute the maximum distance from each pixel to any plane. But for union we want the minimum distance. I also added a "isolating the grand canyon" example to the terrain demo. This should be good to go now! |
Both updates look great. Thanks @OmarShehata! |
Don't merge yet. Opening for feedback. I still have to:
Cesium3DTile.js
.This resolves #6600. I started with the solution in #6616 and noticed that it has some peculiar behavior:
Notice how this is almost correct, except there's a narrow window in which the the whole tileset becomes visible. This is fixed when this scratch matrix is moved inside the function. The reason this inverse of the tile's modelMatrix was multiplied there is because the clipping plane's coordinates should be relative to just the root tile, not to each child, so when Model.js multiplies in each tile's modelMatrix, its inverse cancels its out.
I think a simpler way is to simply save the root tile's matrix and just use that when this model is part of a tileset. This works just like before and fixes the issue in the GIF above.
Tilesets with No Root Transforms
The problem with the other tileset in the original issue is that the root tile had no defined transform, which required you to manually move the clipping plane so that it's relative to the tileset:
Since the user expects the clipping plane to be relative to the tileset, this can be annoying. The proposed solution here uses the tileset's bounding sphere if no root transform is defined.
Tilesets with RTC Coordinates
Another problem was tilesets that are defined with RTC coordinates. The clipping plane thinks the model's coordinates are relative to the center of the earth, so it also required manually moving the clipping planes to the bounding sphere as above. The solution here applies the RTC offset when found.
@lilleyse there are a few things I'm unsure about here:
There's one dataset this doesn't work as I expect it to. On
Cesium3DTiles/Instanced/InstancedRTC
the clipping plane seems tilted:Even though the plane is defined as
new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), 0)
with no other transformation. Here's a localhost:8080 Sandcastle link. I'm not sure why it's rotated that way or if this is expected.Overall, this fixes the original issue as well as a lot of the tilesets under the
Cesium3DTiles/Instanced/
directory, many of which either had no transforms on the root tile or were using RTC coordinates.