OrbPro2 a Cesium distribution

ObjectHorizonSweep

THE SCHEDULE FOR AN OBJECT-GEOMETRY HORIZON SWEEP. A sensor bolted to a mast does not see a clean horizon either, and the thing in its way is not terrain — it is the platform the sensor is standing on. A radar on a ship's foremast is blanked aft by its own funnels, hangar and after mast; STK draws that as a green surface carved out of the sensor volume and refuses access through it ("Constraining Access Based on Visibility"). TerrainHorizonSweep answers the same question for terrain by walking RANGE along each azimuth and reading heights. That schedule is wrong for an object. Terrain is a height field sampled at a position; a superstructure is a closed solid that a ray either hits or misses, and the sampling primitive available for it is a pick, not a height read. So this module walks ELEVATION, not range: THE MASK IS A THRESHOLD IN ELEVATION. Along one azimuth, a ray fired at high elevation clears the structure and a ray fired at low elevation hits it. Because the platform is a solid attached below/around the mount, that transition is what the mask angle IS. The sweep therefore fires a coarse descending ELEVATION LADDER until the first hit, then BISECTS the bracket between the last miss and that first hit. Each halving buys one bit, so the cost is `ladder + ceil(log2(step / tolerance))` picks per azimuth instead of the hundreds a uniform elevation scan at the same tolerance would need — and picks are expensive (each one is an offscreen render). AZIMUTHS TERMINATE ON THE BOUNDING SPHERE. The platform is entirely inside one sphere. Nothing outside that sphere is the platform, so: reach = |center - mount| + radius (no hit is possible past it) topRise = (center.up - mount.up) + radius (nothing can be higher) bound(d) = atan(topRise / d), d <= reach; -infinity beyond Once that bound has fallen to the mask already accumulated for an azimuth, no further sample on it can matter. Unlike the terrain sweep — whose Everest ceiling keeps open-ocean azimuths alive for hundreds of kilometres — an object's bound collapses within metres, which is exactly why the sweep is affordable at pick prices. Everything here is PURE ARITHMETIC. Nothing in this module touches a Scene, a Model or a Ray: a caller supplies hit/miss verdicts from whatever native sampler it has (`Scene.pickFromRay` is the one OrbPro ships), and this module decides where to sample next, when to stop, and how the resulting table becomes sensor geometry. That separation is what lets the guarantees be asserted without a GL context. The output is deliberately the SAME elevation-by-azimuth table TerrainHorizonSweep feeds: one elevation per azimuth bin, azimuth measured from north and increasing with the index, elevation in radians and positive above the mount's horizontal. ObjectHorizonSweep.directions converts it to the custom-sensor boundary, so an object mask and a terrain mask are interchangeable inputs to a `Sensor` of `Type.CUSTOM`.

Members

static Cesium.ObjectHorizonSweep.DEFAULT_AZIMUTH_COUNT : number

Default number of azimuth bins. 72 bins is a 5-degree step: fine enough to resolve a mast from the gap beside it, coarse enough that the whole sweep is a few hundred picks rather than a few thousand.

static Cesium.ObjectHorizonSweep.DEFAULT_LADDER_STEP : number

Default coarse ladder step, in degrees.

static Cesium.ObjectHorizonSweep.DEFAULT_MAXIMUM_ELEVATION : number

Default top of the elevation ladder, in degrees. A structure can be directly overhead of a mount tucked under it, so the ladder starts near the zenith.

static Cesium.ObjectHorizonSweep.DEFAULT_MAXIMUM_SECTOR_SPAN : number

Default widest azimuth span of a single sector, in radians. A convex cone cannot span half the sky, so sectors are split well inside 180 degrees.

static Cesium.ObjectHorizonSweep.DEFAULT_MINIMUM_ELEVATION : number

Default bottom of the elevation ladder, in degrees. Below the mount's own horizontal the platform is not masking sky, it is simply beneath the sensor.

static Cesium.ObjectHorizonSweep.DEFAULT_SECTOR_TOP_ELEVATION : number

Default ceiling for a containment sector, in radians — just short of the zenith so a sector is a proper spherical quadrilateral rather than a degenerate point.

static Cesium.ObjectHorizonSweep.DEFAULT_TOLERANCE : number

Default bisection tolerance, in degrees — the accuracy of a mask edge.

Methods

static Cesium.ObjectHorizonSweep.azimuthForIndex(index, azimuthCount)number

Azimuth bin index -> azimuth from north, in RADIANS. Bin 0 is due north and the index increases clockwise, which is the convention the mask table and every az/el readout in the gallery share.
Name Type Default Description
index number The bin index.
azimuthCount number ObjectHorizonSweep.DEFAULT_AZIMUTH_COUNT optional Azimuth bins.
Returns:
Azimuth from north, in radians.

static Cesium.ObjectHorizonSweep.directions(mask, options)Array.<Spherical>

Mask table -> custom-sensor boundary directions. The sensor's local frame is the mount's east-north-up frame (x=east, y=north, z=up). `Spherical.clock` is measured in the xy-plane from +x, so an azimuth-from-north converts as `clock = atan2(cos(az), sin(az))`, and `cone` is measured from zenith, so `cone = 90 deg - elevation`. The walk is DESCENDING in azimuth, which is ASCENDING in clock — the engine's own CUSTOM winding. The reverse order produces a boundary that renders plausibly but inverts containment, so the drawn volume would stop being the access authority. This is the same convention `maskToDirections` uses in the terrain and az-el mask demos; it lives here so it is asserted once rather than re-derived per demo.
Name Type Description
mask Float64Array | Array.<number> One elevation per azimuth bin, in radians.
options object optional Options.
Name Type Default Description
floorAtHorizon boolean true optional Clamp negative mask elevations to zero.
Returns:
The boundary directions, in CUSTOM winding order.

static Cesium.ObjectHorizonSweep.elevationBound(distance, options)number

The highest elevation angle any part of the platform at or beyond `distance` could still occupy, in DEGREES, from the bounding sphere alone. Negative infinity once `distance` is past the sphere's reach — no geometry remains.
Name Type Description
distance number Distance from the mount, in meters.
options object optional Options.
Name Type Default Description
boundingRadius number 0.0 optional Radius of the platform's bounding sphere, in meters.
centerDistance number 0.0 optional Distance from the mount to the sphere's center, in meters.
centerRise number 0.0 optional How far the sphere's center sits ABOVE the mount, in meters (may be negative).
Returns:
The bound, in degrees.

static Cesium.ObjectHorizonSweep.elevationLadder(options)Float64Array

The coarse elevation ladder, in DEGREES, ordered from the top DOWN. Descending order is not cosmetic: the sweep wants the FIRST hit walking down from open sky, because that hit and the miss above it bracket the mask edge. Walking up from below would bracket the far side of the structure instead.
Name Type Description
options object optional Options.
Name Type Default Description
maximumElevation number ObjectHorizonSweep.DEFAULT_MAXIMUM_ELEVATION optional Top of the ladder, in degrees.
minimumElevation number ObjectHorizonSweep.DEFAULT_MINIMUM_ELEVATION optional Bottom of the ladder, in degrees.
ladderStep number ObjectHorizonSweep.DEFAULT_LADDER_STEP optional Step, in degrees.
Returns:
Elevations in degrees, descending.

static Cesium.ObjectHorizonSweep.isExhausted(maskDegrees, distance, options)boolean

Whether an azimuth is EXHAUSTED at `distance`: the mask already accumulated for it is at least as high as anything further out could reach.
Name Type Description
maskDegrees number The highest elevation seen on this azimuth so far, in degrees.
distance number The distance just sampled, in meters.
options object optional Options, as ObjectHorizonSweep.elevationBound.
Returns:
True when no further sample on this azimuth can raise the mask.

static Cesium.ObjectHorizonSweep.maskFromSamples(samples, options)Float64Array

Reduce hit/miss samples to one mask elevation per azimuth bin. The mask at an azimuth is the HIGHEST elevation at which a ray still struck the platform: above it the sensor sees sky, at and below it the sensor is looking into its own superstructure. Azimuths with no hit at all — clear arcs, which every real mask has — take `floorRadians`, NOT negative infinity: a missing answer left at the floor of the search degenerates that direction into a half-space and inverts the sensor volume (the failure TerrainHorizonSweep's callers hit in 2026-08-14).
Name Type Description
samples Array.<object> Samples, each `{ azimuthIndex, elevation, hit }` with `elevation` in RADIANS.
options object optional Options.
Name Type Default Description
azimuthCount number ObjectHorizonSweep.DEFAULT_AZIMUTH_COUNT optional Azimuth bins.
floorRadians number 0.0 optional Elevation for bins nothing was hit in.
Returns:
One elevation per bin, in radians, indexed by azimuth bin.

static Cesium.ObjectHorizonSweep.reach(options)number

The distance past which no part of the platform can lie — the hard stop for every azimuth, independent of what the sweep has measured.
Name Type Description
options object optional Options, as ObjectHorizonSweep.elevationBound.
Returns:
The reach, in meters.

static Cesium.ObjectHorizonSweep.refinementSteps(ladderStep, tolerance)number

How many bisection steps are needed to pin a mask edge inside `tolerance` once the ladder has bracketed it within `ladderStep`.
Name Type Default Description
ladderStep number ObjectHorizonSweep.DEFAULT_LADDER_STEP optional The bracket width, in degrees.
tolerance number ObjectHorizonSweep.DEFAULT_TOLERANCE optional Required accuracy, in degrees.
Returns:
The number of additional samples per azimuth.

static Cesium.ObjectHorizonSweep.sampleBudget(options)number

The total pick budget for a sweep — what a caller must be willing to pay before it starts, since every sample is an offscreen render.
Name Type Description
options object optional Options, as ObjectHorizonSweep.elevationLadder plus:
Name Type Default Description
azimuthCount number ObjectHorizonSweep.DEFAULT_AZIMUTH_COUNT optional Azimuth bins.
tolerance number ObjectHorizonSweep.DEFAULT_TOLERANCE optional Bisection tolerance, in degrees.
Returns:
An upper bound on the number of samples.

static Cesium.ObjectHorizonSweep.sectors(mask, options)Array.<object>

Decompose a mask into CONVEX containment sectors. WHY THIS EXISTS. A custom sensor's drawn boundary is a general polygon on the sphere, but its CONTAINMENT is evaluated as the intersection of one inward half-space per adjacent direction pair — that is, the CONVEX HULL cone of the directions (Atlas 2026-08-15, `Detection/SDF.cpp` `CustomSDF`). An az/el mask carved by real structure is emphatically NOT convex: every notch contributes a plane that slices the entire volume rather than its own azimuth sector, and the admitted region collapses to a narrow wedge. The picture stays right — the renderer does not use those planes — while access verdicts silently become wrong, which is the worst possible failure for a mask whose entire purpose is to be the access authority. The fix is decomposition. The mask table is piecewise constant by construction: one elevation per azimuth BIN. So the masked-in sky is exactly the union of spherical quadrilaterals — one per run of bins sharing an elevation, bounded below by that elevation, above by `topElevation`, and on the sides by the run's azimuth edges. Each quad IS convex while its azimuth span stays under 180 degrees, so each is a faithful custom-sensor volume, and a target has access iff it is inside ANY of them. Sectors are returned in ascending-clock winding, the same convention ObjectHorizonSweep.directions uses.
Name Type Description
mask Float64Array | Array.<number> One elevation per azimuth bin, in radians.
options object optional Options.
Name Type Default Description
floorAtHorizon boolean true optional Clamp negative mask elevations to zero.
topElevation number ObjectHorizonSweep.DEFAULT_SECTOR_TOP_ELEVATION optional Sector ceiling, in radians.
maximumSpan number ObjectHorizonSweep.DEFAULT_MAXIMUM_SECTOR_SPAN optional Widest azimuth span of one sector, in radians.
Returns:
Sectors, each `{ startAzimuth, endAzimuth, elevation, directions }`.
Need help? The fastest way to get answers is from the community and team on the Cesium Forum.