Created
January 4, 2021 12:56
-
-
Save thw0rted/2eb8834b9db7ec1a677a369e706953c3 to your computer and use it in GitHub Desktop.
Computing visibility of Cesium entities
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.