# How the Gods-Eye-View Icon Orientation System Handles Horizon Culling

> Discover how the Gods-Eye-View icon orientation system uses geometric culling and sky-backdrop blending to handle horizon culling, ensuring smooth fading of entity icons.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: internals
- Published: 2026-08-29

---

**The icon orientation system combines a binary EllipsoidalOccluder for geometric culling with a continuous sky-backdrop blend factor to smoothly fade entity icons when they fall below the visible horizon.**

The [bilawalsidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view) repository implements a robust horizon culling mechanism in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) that prevents flight, vessel, and camera icons from rendering when occluded by the Earth's curvature. This **icon orientation system** handles horizon culling through two complementary mathematical techniques that balance performance efficiency with visual smoothness.

## The Dual-Mechanism Architecture

The implementation relies on two coordinated techniques defined in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js). These mechanisms operate sequentially during each render tick to determine visibility and aesthetic blending.

### Horizon Occluder for Binary Culling

At the core of the system is a reusable `Cesium.EllipsoidalOccluder` instance that performs rapid point-inclusion testing against the WGS84 ellipsoid. According to the source code in lines 26-29, the `horizonOccluder(camera)` function refreshes this singleton with the current camera position each frame:

```javascript
export function horizonOccluder(camera) {
  _occluder.cameraPosition = camera.positionWC;   // ← update camera pose
  return _occluder;
}

```

The occluder's `isPointVisible(pos)` method tests whether a billboard's world position lies behind the planet relative to the camera viewpoint. Because the application uses Google 3D Tiles instead of the default Cesium globe, the depth buffer lacks information for the far side of the Earth. The **EllipsoidalOccluder** mathematically determines occlusion without depth queries, returning a boolean that immediately culls geometry below the horizon.

### Sky-Backdrop Factor for Smooth Transitions

While the occluder provides hard culling, the `skyBackdropFactor` function (lines 70-115) implements a soft fade band that prevents icons from popping at the horizon boundary. This function calculates a continuous blend value based on angular displacement from the horizon line:

```javascript
export function skyBackdropFactor(cameraPosition, position, featherRad = HORIZON_FEATHER_RAD) {
  /* … geometry calculations … */
  const horizonHalfAngle = Math.asin(Math.min(1, 1 / cameraMag));
  const margin = rayAngle - horizonHalfAngle;
  const t = Math.min(1, Math.max(0, margin / (2 * featherRad) + 0.5));
  return t * t * (3 - 2 * t);   // smoothstep blend
}

```

The function accepts a `featherRad` parameter defaulting to approximately **0.019 radians (1.1°)**, defining the transition zone width. Values below the horizon return `0` (ground-background), values above return `1` (sky-background), and the **smoothstep** interpolation (`t * t * (3 - 2 * t)`) ensures perceptually smooth alpha blending across the feather region.

## Practical Implementation in the Rendering Pipeline

Consumer modules integrate these utilities by updating the occluder state each frame and applying visibility tests to billboard entities. The following pattern from [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) demonstrates the standard integration:

```javascript
// Example: Culling a flight billboard each frame
function updateBillboard(billboard, scene) {
  const cam = scene.camera;
  const occluder = horizonOccluder(cam);           // ← refresh occluder
  const visible = occluder.isPointVisible(billboard.position);
  billboard.show = visible;                       // hide if below horizon

  // Optional: apply a sky-fade for labels attached to the billboard
  const blend = skyBackdropFactor(cam.positionWC, billboard.position);
  billboard.labelMaterial.alpha = blend;           // 0 → fully ground, 1 → fully sky
}

```

For stationary icons requiring orientation logic alongside culling, such as those in [`src/data/cctv.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/cctv.js), the system combines rotation stabilization with the same horizon tests:

```javascript
import { screenProjectedRotation, stabilizeScreenRotation } from './iconOrientation.js';

const rotation = screenProjectedRotation(scene, iconPos, courseDeg);
icon.billboard.rotation = stabilizeScreenRotation(icon.prevRotation, rotation);

```

## Integration Across the Codebase

The horizon culling logic extends beyond aviation entities. While [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) applies these methods to commercial flight billboards, [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js) implements **Nadir-stable icon orientation** using the identical `horizonOccluder` logic for military assets. Similarly, [`src/data/cctv.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/cctv.js) employs the occluder for camera-plane orientation calculations, ensuring CCTV monitor planes only render when geometrically visible above the horizon curve.

## Summary

- The **icon orientation system** in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) fuses binary geometric culling with smooth alpha blending to handle horizon visibility for moving entities.
- The **Horizon Occluder** utilizes a `Cesium.EllipsoidalOccluder` updated per-frame via `horizonOccluder(camera)` to mathematically test point visibility against the Earth's ellipsoid (lines 26-29).
- The **Sky-Backdrop Factor** provides a continuous `smoothstep` blend across a 1.1° feather radius to eliminate popping artifacts near the horizon boundary (lines 70-115).
- Production modules including [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js), [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js), and [`src/data/cctv.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/cctv.js) consume these utilities to ensure icons render only when geometrically appropriate.

## Frequently Asked Questions

### How does the horizon occluder handle the lack of depth buffer information for the far side of the globe?

Because Gods-Eye-View uses Google 3D Tiles instead of the default Cesium globe, the depth buffer does not contain depth information for the far side of the Earth. The `Cesium.EllipsoidalOccluder` mathematically calculates whether a point lies behind the planet's silhouette from the camera's perspective using the WGS84 ellipsoid model, eliminating the need for depth-based occlusion queries.

### What is the purpose of the feather radius parameter in skyBackdropFactor?

The `featherRad` parameter defaults to approximately 0.019 radians (1.1°) and defines the angular width of the transition band around the horizon. This creates a smooth fade effect where icons gradually blend between ground and sky backgrounds rather than instantly popping visible or invisible, which improves visual coherence during camera movement.

### Why does the sky-backdrop factor use smoothstep interpolation instead of linear blending?

The function returns `t * t * (3 - 2 * t)`, which is a standard smoothstep curve. This interpolation method provides zero derivatives at the endpoints, creating a visually smooth acceleration and deceleration of the blend factor that appears more natural to human perception than linear interpolation, which would create noticeable velocity changes at the transition boundaries.

### Where is the horizon culling logic applied in the codebase?

The primary implementation resides in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js), but the `horizonOccluder` function is consumed by multiple entity modules including [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) for commercial aviation tracking, [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js) for military asset visualization, and [`src/data/cctv.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/cctv.js) for camera surveillance plane orientation.