Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

@thw0rted 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