Skip to content

Instantly share code, notes, and snippets.

@thw0rted
Created January 4, 2021 12:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thw0rted/2eb8834b9db7ec1a677a369e706953c3 to your computer and use it in GitHub Desktop.
Save thw0rted/2eb8834b9db7ec1a677a369e706953c3 to your computer and use it in GitHub Desktop.
Computing visibility of Cesium entities
// Which entities are in/out of the current viewport? Returns an object where
// the passed items are binned according to whether their location(s) can be
// seen in the current viewport. The object keys are determined by the Cesium
// `Intersect` enum.
public findVisibility<T>(coll: T[], getBounds: BoundsFunction<T>, time?: JulianDate): VisibilityBins<T> {
const ret: VisibilityBins<T> = {
[Intersect.INSIDE]: [],
[Intersect.OUTSIDE]: [],
[Intersect.INTERSECTING]: [],
};
const now = JulianDate.now();
if (this.viewer?.scene.mode === SceneMode.SCENE3D) {
this._findVis3D(coll, getBounds, time || now, ret);
} else {
this._findVis2D(coll, getBounds, time || now, ret);
}
return ret;
}
private _findVis3D<T>(coll: T[], getBounds: BoundsFunction<T>, now: JulianDate, ret: VisibilityBins<T>) {
const bbScratch: OrientedBoundingBox = new OrientedBoundingBox();
const bsScratch: BoundingSphere = new BoundingSphere();
// cullingVolume only works reliably in 3D, see Cesium issue #7917
const cam = this.viewer?.camera;
const cv = cam?.frustum.computeCullingVolume(cam.positionWC, cam.directionWC, cam.upWC);
if (!cv) { return; }
coll.forEach(ent => {
const bounds = getBounds(ent, now);
if (bounds instanceof Rectangle) {
OrientedBoundingBox.fromRectangle(bounds, undefined, undefined, undefined, bbScratch);
try {
// Save item in collection that corresponds to returned Intersect
ret[cv.computeVisibility(bbScratch)].push(ent);
} catch {
// Shouldn't happen
if (isDebug()) { console.info("Failed to compute visibility!"); }
}
} else if (bounds instanceof Cartesian3) {
BoundingSphere.fromPoints([bounds], bsScratch);
try {
// Save item in collection that corresponds to returned Intersect
ret[cv.computeVisibility(bsScratch)].push(ent);
} catch {
// Shouldn't happen
if (isDebug()) { console.info("Failed to compute visibility!"); }
}
} else {
// If we can't understand bounds for an entity, it's always OUTSIDE
ret[Intersect.OUTSIDE].push(ent);
}
});
}
private _findVis2D<T>(coll: T[], getBounds: BoundsFunction<T>, now: JulianDate, ret: VisibilityBins<T>) {
const vpRect = this.getViewportRectangle();
if (!vpRect) {
// If we don't have a viewport polygon in 2D mode, just treat
// everything as being visible.
ret[Intersect.INSIDE] = coll;
return ret;
}
const union = new Rectangle();
const intersection = new Rectangle();
coll.forEach(ent => {
const bounds = getBounds(ent, now);
if (bounds instanceof Rectangle) {
// We only actually care if the intersection is empty or not,
// but pass a scratch object to avoid multiple allocations
const intVal = Rectangle.intersection(bounds, vpRect, intersection);
// If the union of bounds and viewport is the same as the
// viewport, the bounds are entirely inside
Rectangle.union(bounds, vpRect, union);
const fullyContained = union.width < vpRect.width + CesiumMath.EPSILON10 &&
union.height < vpRect.height + CesiumMath.EPSILON10;
if (!intVal) {
// There was no overlap between the viewport and the item bounds
ret[Intersect.OUTSIDE].push(ent);
} else if (fullyContained) {
// The item bounds are totally inside the viewport
ret[Intersect.INSIDE].push(ent);
} else {
// Bounds overlaps with viewport
ret[Intersect.INTERSECTING].push(ent);
}
} else if (bounds instanceof Cartesian3) {
const inside = Rectangle.contains(vpRect, Cartographic.fromCartesian(bounds));
if (inside) {
// There was no overlap between the viewport and the item bounds
ret[Intersect.INSIDE].push(ent);
} else {
// The item bounds are totally inside the viewport
ret[Intersect.OUTSIDE].push(ent);
}
} else {
// If we can't understand bounds for an entity, it's always OUTSIDE
ret[Intersect.OUTSIDE].push(ent);
}
});
}
@thw0rted
Copy link
Author

thw0rted commented Jan 4, 2021

Note that on L60, getViewportRectangle is left as an exercise to the reader. My version is pretty complex and includes logic to guess at where the horizon line lies if you've rotated the camera such that space/background is visible -- and it still gives up completely if you're zoomed out far enough to see the full globe. If there's a built-in version of this that works reliably in all 3 map modes (2D/3D/Columbus), I'd love to know about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment