# How God’s Eye View Ensures Icon Orientation for Aircraft and Ships on the Globe

> Learn how God's Eye View calculates screen-space rotation to orient aircraft and ship icons correctly, stabilizing them for smooth camera movement.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-06

---

**God's Eye View ensures correct icon orientation for aircraft and ships by computing a screen-space rotation that projects each entity's heading vector onto the camera's basis, then stabilizing the result to prevent jitter during camera movements.**

When building a Cesium-based globe visualization, fixed 2D icons (billboards) always face the camera by default. For aviation and maritime tracking applications, this creates a problem: how do you show an aircraft or vessel pointing in its actual direction of travel when the icon itself must rotate to remain visible? The `bilawalsidhu/gods-eye-view` repository solves this through a specialized projection system implemented in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js).

## Screen-Projected Rotation: The Core Algorithm

The primary mechanism is **`screenProjectedRotation(scene, position, courseDeg, previous)`**, located at lines 52-80 of [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js). This function transforms a world-space heading into a 2D screen rotation that accounts for the current camera orientation.

The algorithm follows four steps:

1. **Convert course to local ENU vector** – The heading in degrees is transformed into a forward-probe vector in East-North-Up (ENU) coordinates using `Cesium.Cartesian3.fromElements`.

2. **Transform to world space** – The local vector is multiplied by the ENU-to-fixed-frame matrix via `Cesium.Matrix4.multiplyByPointAsVector`.

3. **Project onto camera basis** – The world-forward vector is dotted with `camera.rightWC` and `camera.upWC` to produce screen-space components `dx` and `dy`.

4. **Compute rotation angle** – `Math.atan2(-dx, -dy)` yields the final rotation in radians, where 0 means the icon points upward on screen.

Degenerate cases—when the icon is behind the camera, off-screen, or the projected vector is below `MIN_SCREEN_COMPONENT_M`—automatically fall back to the previous rotation value. This prevents sudden orientation jumps during rapid camera maneuvers.

```js
// src/data/iconOrientation.js – screen-projected rotation (lines 52-80)
export function screenProjectedRotation(scene, position, courseDeg, previous = null) {
  const camera = scene?.camera;
  if (!camera?.rightWC || !camera?.upWC || !position) return previous;

  const courseRad = Cesium.Math.toRadians(courseDeg || 0);
  Cesium.Cartesian3.fromElements(
    Math.sin(courseRad) * FORWARD_PROBE_M,
    Math.cos(courseRad) * FORWARD_PROBE_M,
    0,
    _scratchForward
  );
  const enu = Cesium.Transforms.eastNorthUpToFixedFrame(position, Cesium.Ellipsoid.WGS84, _scratchEnu);
  Cesium.Matrix4.multiplyByPointAsVector(enu, _scratchForward, _scratchWorldForward);

  const dx = Cesium.Cartesian3.dot(_scratchWorldForward, camera.rightWC);
  const dy = -Cesium.Cartesian3.dot(_scratchWorldForward, camera.upWC);
  if ((dx * dx + dy * dy) < MIN_SCREEN_COMPONENT_M * MIN_SCREEN_COMPONENT_M) return previous;

  // 0 rad = icon pointing up on screen
  return Math.atan2(-dx, -dy);
}

```

## Rotation Stabilization for Jitter-Free Movement

Raw projected rotation would flicker when the camera and entity align nearly parallel to the view direction. To address this, **`stabilizeScreenRotation(previous, next, deadbandRad)`** (lines 92-101) filters out sub-threshold changes.

The stabilizer uses circular arithmetic (`Math.atan2(Math.sin(next - previous), Math.cos(next - previous))`) to find the shortest wrapped difference between angles. Changes below `ROTATION_DEADBAND_RAD` (approximately 0.5°) are suppressed, preventing visual noise without introducing perceptible lag.

```js
// src/data/iconOrientation.js – rotation stabilizer (lines 92-101)
export function stabilizeScreenRotation(previous, next, deadbandRad = ROTATION_DEADBAND_RAD) {
  if (!Number.isFinite(next)) return Number.isFinite(previous) ? previous : null;
  if (!Number.isFinite(previous)) return next;
  const delta = Math.atan2(Math.sin(next - previous), Math.cos(next - previous));
  return Math.abs(delta) < Math.max(0, deadbandRad) ? previous : next;
}

```

## Layer Integration: Aircraft and Ship Icons

The rotation system is consumed by the moving-entity layers through **Cesium `CallbackProperty`** instances that update each frame.

In **[`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js)**, each aircraft billboard's `imageRotation` property is bound to a callback that invokes the projection and stabilization pipeline:

```js
// src/data/flights.js – aircraft billboard rotation binding
const billboard = entity.billboard;
billboard.image = aircraftIcon(iconKind, sizePx);
billboard.imageRotation = new Cesium.CallbackProperty(() => {
  const projRot = screenProjectedRotation(scene, entity.position.getValue(), flight.courseDeg);
  return stabilizeScreenRotation(billboard._lastRot, projRot);
}, false);

```

The same pattern applies to maritime tracking in **[`src/data/vessels.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/vessels.js)** (or equivalent AIS vessel file), ensuring ship icons maintain proper heading alignment across the global fleet.

## Key Design Decisions

- **Billboard-based rendering** – Using Cesium billboards rather than 3D models guarantees pixel-perfect icons at any zoom level and eliminates z-fighting artifacts.

- **Per-frame callback evaluation** – The `CallbackProperty` with `isConstant = false` ensures rotation updates continuously during camera orbits, pitch changes, and entity movement.

- **Scratch variable reuse** – Functions in [`iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/iconOrientation.js) use module-level scratch variables (`_scratchForward`, `_scratchEnu`, `_scratchWorldForward`) to minimize GC pressure during high-frequency updates.

- **Graceful degradation** – When projection fails or the icon leaves view, the system retains the last valid rotation rather than defaulting to zero or arbitrary values.

## Summary

- **`screenProjectedRotation`** in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) transforms entity headings into camera-relative screen rotations using vector projection onto `camera.rightWC` and `camera.upWC`.

- **`stabilizeScreenRotation`** applies a 0.5° deadband to prevent orientation jitter during near-degenerate viewing angles.

- **Aircraft and ship layers** in [`flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/flights.js) and [`vessels.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/vessels.js) bind these utilities to billboard `imageRotation` properties via `CallbackProperty` for continuous per-frame updates.

- The system handles edge cases—off-screen entities, behind-camera positions, and minimal screen components—through automatic fallback to previous rotation values.

## Frequently Asked Questions

### What file contains the icon orientation logic for God’s Eye View?

The core implementation resides in **[`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js)**, which exports `screenProjectedRotation` and `stabilizeScreenRotation` for use by aircraft and vessel layers.

### Why use screen-space rotation instead of 3D models for aircraft and ships?

Screen-space rotation with **billboards** provides sharper icon rendering at all zoom levels, eliminates z-fighting, and maintains consistent visual weight. The projection system preserves the semantic value of heading information without the performance and visual complexity of articulated 3D models.

### How does the system prevent icon jitter when the camera is directly above an entity?

The **`stabilizeScreenRotation`** function filters angular changes below approximately 0.5° (`ROTATION_DEADBAND_RAD`). When the camera approaches nadir and the forward vector projects to near-zero screen components, the deadband prevents rapid oscillation while the fallback mechanism in `screenProjectedRotation` retains the last stable orientation.

### Can this orientation system work with custom icon sets?

Yes. The rotation value produced by `screenProjectedRotation` is applied to any billboard through the standard `imageRotation` property. The companion file [`src/data/aircraftIcon.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/aircraftIcon.js) generates billboard images, but the orientation logic is decoupled from icon assets and works with any image source.