# How God's Eye View Calculates Icon Orientation in Screen Space

> Discover how God's Eye View calculates icon orientation in screen space by projecting real-world vectors onto camera basis. Learn to align billboard icons with ground tracks, improving map clarity.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: deep-dive
- Published: 2026-09-11

---

**God's Eye View calculates icon orientation in screen space by projecting the entity's real-world course vector onto the camera's right and up basis vectors, then deriving the rotation angle using `atan2` to align billboard icons with their true ground track regardless of camera heading.**

When rendering moving entities as billboards in a 3D geospatial visualization, standard world-space rotations fail to indicate true direction when the camera orbits or tracks objects. The **bilawalsidhu/gods-eye-view** repository solves this by calculating icon orientation directly in screen space, ensuring that aircraft and vehicle icons always point along their actual course vectors even during complex camera maneuvers where traditional surface-normal alignment breaks down.

## The Screen Space Projection Approach

The core implementation in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js) abandons surface-normal alignment in favor of a **camera-basis projection**. This technique transforms the entity's local course into screen-space components that remain visually accurate during tracked orbits and off-screen projections.

### Converting Course to World Space

The algorithm begins with the entity's course as a local forward vector in **ENU (East-North-Up)** coordinates. According to the source code in [[`iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/iconOrientation.js) lines 14-22](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js#L14-L22), the system creates a probe point **2000 meters** ahead of the entity's current position to establish a stable direction vector.

```javascript
// Simplified illustration of the world-space conversion
const localForward = new Cartesian3(
  Math.sin(radians(courseDeg)),
  Math.cos(radians(courseDeg)),
  0.0
);
const worldForward = Matrix4.multiplyByPointAsVector(
  transform,
  localForward,
  new Cartesian3()
);

```

If the projected length falls below `MIN_SCREEN_COMPONENT_M`, the orientation is considered undefined and the previous rotation is preserved. This prevents erratic behavior when entities move directly toward or away from the camera, which would otherwise cause the projected vector to vanish.

### Projecting onto Camera Basis Vectors

Once in world space, the forward vector is projected onto the camera's orthonormal basis to obtain screen-space displacement values. The implementation uses Cesium's camera properties `camera.rightWC` and `camera.upWC` (world coordinates) to extract horizontal and vertical components:

- **dx**: Dot product of world forward with `camera.rightWC`
- **dy**: Dot product of world forward with `camera.upWC`

This projection effectively flattens the 3D course vector into the 2D screen plane, yielding components that represent how the icon should orient itself relative to the screen edges.

### Computing the Rotation Angle

With screen-space components `dx` and `dy` calculated, the final rotation is determined using `atan2` as shown in [[`iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/iconOrientation.js) lines 66-80](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js#L66-L80):

```javascript
const rotation = Math.atan2(-dx, -dy);

```

The negative signs ensure that **zero rotation aligns the icon with screen-up**. This convention keeps the icon pointing toward the top of the display when the entity travels northward in a north-up view, maintaining intuitive visual alignment for operators.

## Stabilization and Jitter Filtering

Raw screen-space calculations introduce sub-degree jitter when entities move slowly or when floating-point precision varies. The `stabilizeScreenRotation` function implements a **dead-band filter** with a threshold of **0.5°** (approximately **0.0087 radians**) to eliminate visual noise.

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

// Example: update a billboard’s rotation each frame
function updateBillboardRotation(billboard, scene, position, courseDeg) {
  const projected = screenProjectedRotation(
    scene,
    position,
    courseDeg,
    billboard.rotation   // fallback if projection fails
  );
  const stable = stabilizeScreenRotation(billboard.rotation, projected);
  if (stable !== null) billboard.rotation = stable;
}

```

As implemented in [[`iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/iconOrientation.js) lines 82-100](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js#L82-L100), this function only updates the rotation when the angular difference exceeds the dead-band. This prevents distracting micro-rotations while ensuring responsive updates during actual course changes.

## Production Implementation

The screen-space orientation system is integrated into the flight tracking modules at [[`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js)](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) and [[`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js)](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js). The following pattern demonstrates how billboard rotations are synchronized each frame:

```javascript
// In the flight data module (simplified)
function _syncTracked2dRotation() {
  const pos = getTrackedPosition();
  const projected = screenProjectedRotation(
    viewer.scene,
    pos,
    getTrackedCourse(),
    lastTrackedRotation
  );
  const rotation = stabilizeScreenRotation(lastTrackedRotation, projected, 0);
  if (rotation !== null) lastTrackedRotation = rotation;
}

```

This implementation handles edge cases where the forward probe point would be behind the camera or off-screen during **>180° tracked orbits**, maintaining stable visual orientation even when the entity itself is not fully visible.

## Summary

- **Camera-basis projection** replaces surface-normal alignment to maintain accurate icon direction during tracked orbits and complex camera movements.
- The algorithm projects a 2000-meter course vector onto `camera.rightWC` and `camera.upWC` to derive screen-space `dx` and `dy` components.
- **Rotation calculation** uses `atan2(-dx, -dy)` to align icons with screen-up when traveling northward.
- A **0.5° dead-band filter** in `stabilizeScreenRotation` eliminates sub-degree jitter while preserving responsive course updates.
- The system gracefully handles undefined orientations by preserving previous rotations when projections fall below `MIN_SCREEN_COMPONENT_M`.

## Frequently Asked Questions

### Why project onto the camera's right and up vectors instead of using world coordinates?

Billboards are screen-facing quads that always rotate to face the camera. World-space rotations would cause icons to tilt awkwardly or point in visually incorrect directions when the camera banks or orbits. By projecting onto the camera's right and up basis vectors, the system calculates rotation relative to the actual screen plane, ensuring icons point along their ground track from the viewer's perspective regardless of camera attitude.

### What is the significance of the 2000 meter forward vector?

The 2000-meter probe distance provides a stable sample point for determining course direction without being affected by minor position jitter or floating-point precision errors at the entity's exact location. This distance is long enough to maintain directional stability for high-altitude flights yet short enough to remain valid for surface vehicles, ensuring the projection remains meaningful across different entity types.

### How does the stabilization prevent icon jitter?

The `stabilizeScreenRotation` function implements a hysteresis mechanism that only updates the rotation when the computed angle differs from the current angle by more than 0.5 degrees (0.0087 radians). This dead-band filter ignores noise-induced micro-variations in the projection calculation while allowing immediate updates when the entity actually changes course by a meaningful amount.

### Where is the icon orientation logic implemented in the codebase?

The core logic resides in [`src/data/iconOrientation.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/iconOrientation.js), specifically the `screenProjectedRotation` function (lines 14-22) for projection calculations and `stabilizeScreenRotation` (lines 82-100) for filtering. Production usage appears in [`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) and [`src/data/militaryFlights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryFlights.js), where these utilities update billboard rotations for commercial and military flight tracking respectively.