Skip to content
Back to skills

Cesiumjs Primitives

ASecurity

CesiumJS primitives and geometry - Primitive, GeometryInstance, Appearance, BufferPrimitive collections, GeoJsonPrimitive, Billboard/Label/PointPrimitive collections, built-in geometry shapes, ground primitives, classification. Use when rendering performance-critical static or vector geometry, loading GeoJSON without entities, creating custom shapes, batching draw calls, or using low-level collections.

  • 178 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
ai-agentsrustgoapiperformance

Works with

  • api

Security analysis

A100/100

Scanned September 22, 2026

npx -y skills add CesiumGS/cesiumjs-skills --skill cesiumjs-primitives --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cesiumjs Primitives?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cesiumjs Primitives
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/cesiumgs-cesiumjs-primitives/badge)](https://www.skillsdirectory.com/skills/cesiumgs-cesiumjs-primitives)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cesiumjs-primitives
description: "CesiumJS primitives and geometry - Primitive, GeometryInstance, Appearance, BufferPrimitive collections, GeoJsonPrimitive, Billboard/Label/PointPrimitive collections, built-in geometry shapes, ground primitives, classification. Use when rendering performance-critical static or vector geometry, loading GeoJSON without entities, creating custom shapes, batching draw calls, or using low-level collections."
---
# CesiumJS Primitives & Geometry

> **Applies to:** CesiumJS v1.144+ (ES module imports, `??` instead of `defaultValue`)

## Architecture

The Primitive API is the low-level rendering layer beneath the Entity API, trading convenience for performance.

**Core formula:** `Primitive = GeometryInstance[] + Appearance`

- **GeometryInstance** -- positions a Geometry in world space with per-instance attributes (color, show).
- **Geometry** -- vertex data describing a shape (polygon, box, ellipsoid, etc.).
- **Appearance** -- GLSL shaders + render state + optional Material that shade the geometry.

Primitives are **immutable after first render** -- geometry cannot change, but per-instance attributes update via `primitive.getGeometryInstanceAttributes(id)`.

## Primitive

```js
import {
  Viewer, Primitive, GeometryInstance, EllipseGeometry,
  EllipsoidSurfaceAppearance, Material, Cartesian3, Math as CesiumMath,
} from "cesium";

const viewer = new Viewer("cesiumContainer");
const scene = viewer.scene;

const primitive = scene.primitives.add(new Primitive({
  geometryInstances: new GeometryInstance({
    geometry: new EllipseGeometry({
      center: Cartesian3.fromDegrees(-100.0, 40.0),
      semiMinorAxis: 250000.0,
      semiMajorAxis: 400000.0,
      rotation: CesiumMath.PI_OVER_FOUR,
      vertexFormat: EllipsoidSurfaceAppearance.VERTEX_FORMAT, // must match appearance
    }),
    id: "myEllipse", // returned by Scene.pick()
  }),
  appearance: new EllipsoidSurfaceAppearance({ material: Material.fromType("Stripe") }),
}));
```

### Key Options

| Option | Default | Purpose |
|---|---|---|
| `geometryInstances` | -- | Single instance or array |
| `appearance` | -- | Shading (Appearance subclass) |
| `show` | `true` | Toggle visibility |
| `modelMatrix` | `Matrix4.IDENTITY` | Transform all instances |
| `asynchronous` | `true` | Build geometry on web worker |
| `releaseGeometryInstances` | `true` | Free geometry after GPU upload |
| `allowPicking` | `true` | `false` saves GPU memory |
| `shadows` | `ShadowMode.DISABLED` | Cast/receive shadows |

## Batching Multiple Instances

All instances in one Primitive share a single draw call.

```js
import {
  Primitive, GeometryInstance, RectangleGeometry, EllipseGeometry,
  PerInstanceColorAppearance, ColorGeometryInstanceAttribute,
  Cartesian3, Rectangle, Color,
} from "cesium";

scene.primitives.add(new Primitive({
  geometryInstances: [
    new GeometryInstance({
      geometry: new RectangleGeometry({
        rectangle: Rectangle.fromDegrees(-140, 30, -100, 40),
        vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
      }),
      id: "rect",
      attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.RED.withAlpha(0.5)) },
    }),
    new GeometryInstance({
      geometry: new EllipseGeometry({
        center: Cartesian3.fromDegrees(-80, 35),
        semiMinorAxis: 200000.0,
        semiMajorAxis: 300000.0,
        vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
      }),
      id: "ellipse",
      attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.BLUE.withAlpha(0.5)) },
    }),
  ],
  appearance: new PerInstanceColorAppearance(),
}));
```

### Batching Volume Geometry (CylinderGeometry Grid)

Volume geometry (Cylinder, Box, Ellipsoid) must be positioned via `modelMatrix` on each GeometryInstance. Use `Matrix4.multiply` to combine a world-space anchor with a local offset, then batch all instances into one Primitive.

```js
import {
  Primitive, GeometryInstance, CylinderGeometry,
  PerInstanceColorAppearance, ColorGeometryInstanceAttribute,
  Cartesian3, Matrix4, Transforms, Color, Math as CesiumMath,
} from "cesium";

const center = Cartesian3.fromDegrees(-73.9857, 40.7580);
const anchorFrame = Transforms.eastNorthUpToFixedFrame(center, undefined, new Matrix4());
const instances = [];
const GRID = 10;
const SPACING = 50; // metres

for (let row = 0; row < GRID; row++) {
  for (let col = 0; col < GRID; col++) {
    const xOffset = (col - GRID / 2) * SPACING;
    const yOffset = (row - GRID / 2) * SPACING;
    // Combine anchor ENU frame with a local XYZ offset
    const modelMatrix = Matrix4.multiply(
      anchorFrame,
      Matrix4.fromTranslation(new Cartesian3(xOffset, yOffset, 100), new Matrix4()),
      new Matrix4(),
    );
    instances.push(new GeometryInstance({
      geometry: new CylinderGeometry({
        length: 200,
        topRadius: 8,
        bottomRadius: 8,
        vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
      }),
      modelMatrix,
      attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.fromRandom({ alpha: 1.0 })) },
    }));
  }
}

scene.primitives.add(new Primitive({
  geometryInstances: instances,
  appearance: new PerInstanceColorAppearance({ flat: true }),
}));

// Frame the grid so the full batch is visible -- a shallow pitch hides cylinders
// behind the foreground; a near-nadir pitch flattens them. Aim for ~-45° (-PI/4)
// at a range that covers the grid footprint (GRID * SPACING) with margin.
const range = GRID * SPACING * 3; // ~1500 m for a 10x10x50m grid
viewer.camera.lookAt(
  center,
  new Cartesian3(0, -range * 0.7, range * 0.7), // offset south + up for -45° pitch
);
```

**Key patterns:**
- `Matrix4.multiply(anchorFrame, Matrix4.fromTranslation(offset, result), result)` -- compose the ENU frame at a geographic anchor with a local East/North/Up translation.
- `Color.fromRandom({ alpha: 1.0 })` produces fully-opaque random colours suitable for rainbow-coloured batches.
- **Framing matters:** for a grid of vertical volumes, prefer a `~-45°` (`-CesiumMath.PI_OVER_FOUR`, ~`-0.785` rad) pitch at a range of roughly `3 * gridFootprint`. Pitches shallower than ~`-0.6` rad can push the grid off-screen or hide it behind buildings; nadir views flatten cylinders into dots and lose the batched "field" appearance. A `-0.61` rad pitch is still shallow enough to leave the grid completely out of frame.
- **Verify visibility:** after `primitive.ready`, prefer `viewer.camera.flyToBoundingSphere(primitive._boundingSpheres[0], { duration: 0 })` (or `viewer.flyTo(primitive)`) over hand-tuned offsets if the scenario only requires "the batch is visible".

## Updating Per-Instance Attributes

```js
import { ColorGeometryInstanceAttribute, ShowGeometryInstanceAttribute } from "cesium";

// Wait for async geometry compilation
const removeListener = scene.postRender.addEventListener(() => {
  if (!primitive.ready) return;
  const attrs = primitive.getGeometryInstanceAttributes("rect");
  attrs.color = ColorGeometryInstanceAttribute.toValue(Color.YELLOW);
  attrs.show = ShowGeometryInstanceAttribute.toValue(true);
  removeListener();
});
```

## PrimitiveCollection

Nestable container -- `scene.primitives` is itself a PrimitiveCollection.

```js
import { PrimitiveCollection, BillboardCollection, LabelCollection } from "cesium";

const group = new PrimitiveCollection();
group.add(new BillboardCollection());
group.add(new LabelCollection());
scene.primitives.add(group);
group.show = false; // toggle all children
```

## Choosing a Vector Data Path

| Need | Use |
|---|---|
| Entity lifecycle, clustering, per-entity styling, time-dynamic values | `GeoJsonDataSource` in `cesiumjs-entities` |
| One large GeoJSON object with low overhead and primitive-level performance | `GeoJsonPrimitive` in this skill |
| Tiled vector data, 3D Tiles LOD, metadata styling, feature picking | `MVTDataProvider` in `cesiumjs-3d-tiles` |
| Fully manual high-throughput point/polyline/polygon buffers | `BufferPointCollection`, `BufferPolylineCollection`, `BufferPolygonCollection` |

## Buffer Primitive Collections (Experimental, 1.140+)

Use `BufferPointCollection`, `BufferPolylineCollection`, and `BufferPolygonCollection`
for very large vector datasets where Entity/DataSource overhead is too high. These
APIs were introduced in 1.140 (#13212) and refined through 1.142; they are
experimental and use flyweight primitive objects: reuse one `BufferPoint`,
`BufferPolyline`, or `BufferPolygon` when adding or iterating thousands of items.

```js
import {
  BlendOption,
  BoundingSphere,
  BufferPoint,
  BufferPointCollection,
  BufferPointMaterial,
  Cartesian3,
  Color,
} from "cesium";

const positions = [
  Cartesian3.fromDegrees(-75.16, 39.95),
  Cartesian3.fromDegrees(-73.98, 40.75),
];

const points = scene.primitives.add(new BufferPointCollection({
  primitiveCountMax: positions.length,
  allowPicking: true,
  blendOption: BlendOption.TRANSLUCENT,
  boundingVolume: BoundingSphere.fromPoints(positions), // world space in 1.142+
}));

const point = new BufferPoint();
const material = new BufferPointMaterial({
  color: Color.CYAN.withAlpha(0.65),
  outlineColor: Color.WHITE.withAlpha(0.9),
  outlineWidth: 2,
  size: 10,
});

positions.forEach((position, featureId) => {
  points.add({
    position,
    featureId,
    material,
  }, point);
});

const picked = scene.pick(windowPosition);
if (picked?.collection === points) {
  console.log(picked.index, picked.primitive.featureId);
}
```

> **Breaking change (1.141, #13448):** `BufferPrimitiveCollection.modelMatrix`,
> `boundingVolume`, and `boundingVolumeWC` are now **readonly** -- you may mutate
> the object in place, but reassigning the property (`collection.modelMatrix = ...`)
> throws. Update the existing matrix/volume instead of swapping in a new one.

1.142 notes:
- `boundingVolume` is now world-space, not local/model-space. If you provide it manually, include the collection `modelMatrix` transform yourself.
- Providing `boundingVolume` skips automatic recomputation; this helps large animated collections but makes you responsible for keeping the volume valid.
- `blendOption` is supported on all three buffer collections and enables alpha from `BufferPrimitiveMaterial#color`; `BufferPointCollection` also honors `outlineColor.alpha`.
- Use `BlendOption.OPAQUE` only when every material is fully opaque; use `TRANSLUCENT` or mixed blending when alpha varies.

In 1.143, `BufferPointCollection` no longer leaks `outlineColor` into the fill
when `outlineWidth` is `0`. Set the width to `0` to disable outlines; remove
transparent-outline workarounds that would otherwise complicate batching.

## GeoJsonPrimitive (Experimental, 1.142+)

`GeoJsonPrimitive` loads GeoJSON directly into buffer primitive collections,
bypassing `GeoJsonDataSource` and the Entity layer. Prefer it for large static
or bulk-updated vector datasets. Keep using `GeoJsonDataSource` when you need
Entity conveniences, time-dynamic properties, clustering, or DataSource lifecycle
integration.

```js
import { GeoJsonPrimitive } from "cesium";

const counties = await GeoJsonPrimitive.fromUrl("/data/counties.geojson", {
  allowPicking: true,
});
scene.primitives.add(counties);

console.log(counties.featureCount);
console.log(counties.points);    // BufferPointCollection | undefined
console.log(counties.polylines); // BufferPolylineCollection | undefined
console.log(counties.polygons);  // BufferPolygonCollection | undefined

// Picking returns the GeoJsonPrimitive pick object, including source properties.
const picked = scene.pick(windowPosition);
if (picked?.parentPrimitive === counties) {
  const featureId = picked.primitive.featureId;
  console.log(counties.getId(featureId));
  console.log(counties.getProperties(featureId));
}
```

`GeoJsonPrimitive.fromGeoJson(parsedObject)` is available when the GeoJSON is
already in memory. Source feature IDs are exposed through `ids`/`getId()`, and
source properties through `properties`/`getProperties()`.

## Built-in Geometry Types (31)

All geometries take shape parameters and a `vertexFormat` matching the Appearance. Most have a paired `*OutlineGeometry`. Outlines require a separate Primitive.

### Filled + Outline Pattern

```js
import {
  Primitive, GeometryInstance, PolygonGeometry, PolygonOutlineGeometry,
  PolygonHierarchy, PerInstanceColorAppearance, ColorGeometryInstanceAttribute,
  Cartesian3, Color,
} from "cesium";

const positions = Cartesian3.fromDegreesArray([-115, 37, -115, 32, -107, 33, -102, 35]);

// Fill primitive
scene.primitives.add(new Primitive({
  geometryInstances: new GeometryInstance({
    geometry: new PolygonGeometry({
      polygonHierarchy: new PolygonHierarchy(positions),
      vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
    }),
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.CYAN.withAlpha(0.5)) },
  }),
  appearance: new PerInstanceColorAppearance(),
}));

// Outline primitive (separate draw call)
scene.primitives.add(new Primitive({
  geometryInstances: new GeometryInstance({
    geometry: new PolygonOutlineGeometry({ polygonHierarchy: new PolygonHierarchy(positions) }),
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.WHITE) },
  }),
  appearance: new PerInstanceColorAppearance({ flat: true }),
}));
```

### Geometry Catalog

Every `XxxGeometry` has a matching `XxxOutlineGeometry` unless noted.

**Surface** (work with GroundPrimitive): `CircleGeometry`, `CorridorGeometry`, `EllipseGeometry`, `PolygonGeometry`, `RectangleGeometry`.

**Volume** (need `modelMatrix`): `BoxGeometry` (`fromDimensions()`), `CylinderGeometry` (cone when topRadius != bottomRadius), `EllipsoidGeometry`, `SphereGeometry`, `FrustumGeometry`, `PlaneGeometry`.

**Path**: `CorridorGeometry` (buffered path), `PolylineVolumeGeometry` (2D shape extruded along path), `WallGeometry` (vertical curtain).

**Polygon**: `PolygonGeometry` (holes via `PolygonHierarchy`), `CoplanarPolygonGeometry` (non-Earth-surface).

**Line** (no outline): `PolylineGeometry` (pixel-width), `SimplePolylineGeometry` (1px), `GroundPolylineGeometry` (GroundPolylinePrimitive only).

### Positioning Off-Surface Geometry

Box, Ellipsoid, Cylinder, and Frustum need a `modelMatrix` on the GeometryInstance.

```js
import { GeometryInstance, BoxGeometry, PerInstanceColorAppearance,
  ColorGeometryInstanceAttribute, Cartesian3, Matrix4, Transforms, Color } from "cesium";

const modelMatrix = Matrix4.multiplyByTranslation(
  Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(-105, 40)),
  new Cartesian3(0, 0, 250000), new Matrix4(),
);
new GeometryInstance({
  geometry: BoxGeometry.fromDimensions({
    dimensions: new Cartesian3(400000, 300000, 500000),
    vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
  }),
  modelMatrix,
  id: "floatingBox",
  attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.CORAL) },
});
```

## Appearances (7 Types)

| Appearance | Use Case | Material? |
|---|---|---|
| `PerInstanceColorAppearance` | Per-instance color | No |
| `MaterialAppearance` | Arbitrary geometry + Material | Yes |
| `EllipsoidSurfaceAppearance` | Surface geometry + Material (fewer attrs) | Yes |
| `PolylineColorAppearance` | Per-instance color polylines | No |
| `PolylineMaterialAppearance` | Polylines with Material | Yes |
| `DebugAppearance` | Visualize vertex attributes | No |
| `Appearance` | Base class / custom shaders | Optional |

The geometry `vertexFormat` **must** match the appearance. Use the appearance's static `VERTEX_FORMAT`. For `PerInstanceColorAppearance` without lighting, use `FLAT_VERTEX_FORMAT`.

### MaterialAppearance Example

```js
import { Primitive, GeometryInstance, WallGeometry, MaterialAppearance, Material, Cartesian3 } from "cesium";

scene.primitives.add(new Primitive({
  geometryInstances: new GeometryInstance({
    geometry: new WallGeometry({
      positions: Cartesian3.fromDegreesArrayHeights([-115, 44, 200000, -110, 44, 200000, -105, 44, 200000]),
      vertexFormat: MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat,
    }),
  }),
  appearance: new MaterialAppearance({
    material: Material.fromType("Checkerboard"),
    faceForward: true, // shade both sides
  }),
}));
```

## GroundPrimitive

Drapes geometry onto terrain/3D Tiles. Supported: `CircleGeometry`, `CorridorGeometry`, `EllipseGeometry`, `PolygonGeometry`, `RectangleGeometry`. Add to `scene.primitives` (not `scene.groundPrimitives` -- both work but `scene.primitives` is the conventional target when using a public basemap without Ion terrain).

```js
import { GroundPrimitive, GeometryInstance, PolygonGeometry, PolygonHierarchy,
  PerInstanceColorAppearance, ColorGeometryInstanceAttribute, ClassificationType,
  Cartesian3, Color } from "cesium";

scene.primitives.add(new GroundPrimitive({
  geometryInstances: new GeometryInstance({
    geometry: new PolygonGeometry({
      polygonHierarchy: new PolygonHierarchy(
        Cartesian3.fromDegreesArray([-112, 36, -112, 36.1, -111.9, 36.1]),
      ),
    }),
    id: "groundPolygon",
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.RED.withAlpha(0.5)) },
  }),
  appearance: new PerInstanceColorAppearance({ flat: true, translucent: true }),
  classificationType: ClassificationType.TERRAIN, // TERRAIN, CESIUM_3D_TILE, or BOTH
}));
```

## GroundPolylinePrimitive

Drapes a polyline on terrain. Add to `scene.primitives` (not `scene.groundPrimitives`).

```js
import { GroundPolylinePrimitive, GeometryInstance, GroundPolylineGeometry,
  PolylineColorAppearance, ColorGeometryInstanceAttribute, Cartesian3, Color } from "cesium";

scene.primitives.add(new GroundPolylinePrimitive({
  geometryInstances: new GeometryInstance({
    geometry: new GroundPolylineGeometry({
      positions: Cartesian3.fromDegreesArray([-112.13, 36.05, -112.09, 36.10, -112.13, 36.17]),
      width: 4.0,
      loop: true,
    }),
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.LIME.withAlpha(0.7)) },
  }),
  appearance: new PolylineColorAppearance(),
}));
```

## ClassificationPrimitive

Highlights volumes classifying terrain or 3D Tiles. Valid: `BoxGeometry`, `CylinderGeometry`, `EllipsoidGeometry`, `PolylineVolumeGeometry`, `SphereGeometry`, plus extruded surface geometries.

```js
import { ClassificationPrimitive, GeometryInstance, BoxGeometry, PerInstanceColorAppearance,
  ColorGeometryInstanceAttribute, ClassificationType, Cartesian3, Transforms, Color } from "cesium";

scene.primitives.add(new ClassificationPrimitive({
  geometryInstances: new GeometryInstance({
    geometry: BoxGeometry.fromDimensions({
      dimensions: new Cartesian3(100, 100, 50),
      vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
    }),
    modelMatrix: Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(-75.59, 40.04, 25)),
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.YELLOW.withAlpha(0.5)) },
  }),
  classificationType: ClassificationType.BOTH,
}));
```

## BillboardCollection

GPU-efficient viewport-aligned images -- far more performant than entities at scale.

> **Breaking change (1.140, #13253):** `BillboardCollection` and `LabelCollection`
> now require WebGL 2, or WebGL 1 with `ANGLE_instanced_arrays` and
> `MAX_VERTEX_TEXTURE_IMAGE_UNITS > 0`. On unsupported devices they no longer
> render -- gate on `scene.context.webgl2` (or feature-detect the extension) if you
> still target legacy WebGL 1 hardware.

### Basic Usage

> **Compatibility fix (1.143):** billboard image loading no longer crashes when
> an application replaces the global `Promise` implementation. Use the public
> `image` property with a URL, loaded image, or canvas; use `setImage` for a
> `Resource` or callback. Do not branch on native `Promise` identity or retain
> compatibility shims for this bug.

```js
import { BillboardCollection, Cartesian3, Color, NearFarScalar,
  HeightReference, HorizontalOrigin, VerticalOrigin } from "cesium";

const billboards = scene.primitives.add(new BillboardCollection({ scene }));
const b = billboards.add({
  position: Cartesian3.fromDegrees(-75.59, 40.04),
  image: "marker.png",
  horizontalOrigin: HorizontalOrigin.CENTER,
  verticalOrigin: VerticalOrigin.BOTTOM,
  heightReference: HeightReference.CLAMP_TO_GROUND,
  scaleByDistance: new NearFarScalar(1000, 1.5, 1e7, 0.3),
});
b.position = Cartesian3.fromDegrees(-75.60, 40.05); // update dynamically
billboards.remove(b);
```

### PinBuilder -- Procedural Pin Images

`PinBuilder` generates canvas-based pin icons at runtime without external image files. Use `fromColor` for solid-colour pins or `fromText` for labelled pins. Pass the returned canvas as the billboard `image`.

When a scenario specifies an ordered list of cities/items and a colour-by-index scheme, **iterate the source array in the given order** and use the loop index directly as the HSL hue index. Re-ordering the source list (e.g. sorting by latitude) swaps which colour lands on which city and fails visual checks.

```js
import { BillboardCollection, PinBuilder, Cartesian3, Color, VerticalOrigin } from "cesium";

const pinBuilder = new PinBuilder();
const cities = [
  { name: "Boston",       lng: -71.0589, lat: 42.3601 },
  { name: "New York",     lng: -74.0060, lat: 40.7128 },
  { name: "Philadelphia", lng: -75.1652, lat: 39.9526 },
  { name: "Washington DC",lng: -77.0369, lat: 38.9072 },
  { name: "Miami",        lng: -80.1918, lat: 25.7617 },
];

const billboards = scene.primitives.add(new BillboardCollection({ scene }));

cities.forEach((city, index) => {
  billboards.add({
    position: Cartesian3.fromDegrees(city.lng, city.lat),
    // Color.fromHsl(hue 0-1, saturation, lightness) produces evenly-spaced hues
    image: pinBuilder.fromColor(Color.fromHsl(index / cities.length, 0.8, 0.5), 48),
    verticalOrigin: VerticalOrigin.BOTTOM,
  });
});

// Text label pin: pinBuilder.fromText("A", Color.ROYALBLUE, 48)
// fromColor / fromText return a canvas -- pass directly as image
```

**`Color.fromHsl(hue, saturation, lightness)`** -- generates colours across the spectrum by varying `hue` (0–1 wraps full circle). Useful for rainbow-colouring N items: `Color.fromHsl(i / n, 0.8, 0.5)`. **`Color.fromRandom({ alpha })`** -- random hue/saturation/lightness with fixed alpha.

## LabelCollection

```js
import { LabelCollection, Cartesian3, Cartesian2, Color, LabelStyle, VerticalOrigin } from "cesium";

const labels = scene.primitives.add(new LabelCollection({ scene }));
labels.add({
  position: Cartesian3.fromDegrees(-75.59, 40.04, 300),
  text: "Philadelphia",
  font: "16px sans-serif",
  fillColor: Color.WHITE,
  outlineColor: Color.BLACK,
  outlineWidth: 2,
  style: LabelStyle.FILL_AND_OUTLINE,
  verticalOrigin: VerticalOrigin.BOTTOM,
  pixelOffset: new Cartesian2(0, -10),
});
```

## PointPrimitiveCollection

```js
import { PointPrimitiveCollection, Cartesian3, Color, NearFarScalar } from "cesium";

const points = scene.primitives.add(new PointPrimitiveCollection());
points.add({
  position: Cartesian3.fromDegrees(-75.59, 40.04),
  pixelSize: 10,
  color: Color.YELLOW,
  outlineColor: Color.BLACK,
  outlineWidth: 2,
  scaleByDistance: new NearFarScalar(1000, 1.0, 1e7, 0.1),
});
```

## CloudCollection and PolylineCollection

```js
import { CloudCollection, PolylineCollection, Cartesian3, Cartesian2, Color, Material } from "cesium";

// Procedural cumulus clouds
const clouds = scene.primitives.add(new CloudCollection());
clouds.add({
  position: Cartesian3.fromDegrees(-75.59, 40.04, 1500),
  scale: new Cartesian2(40, 12),
  maximumSize: new Cartesian3(40, 12, 15),
  slice: 0.36,
});

// Low-level polyline collection
const polylines = scene.primitives.add(new PolylineCollection());
polylines.add({
  positions: Cartesian3.fromDegreesArray([-75, 40, -70, 42, -65, 38]),
  width: 3.0,
  material: Material.fromType("Color", { color: Color.AQUA }),
});
```

## Polyline via Primitive

```js
import { Primitive, GeometryInstance, PolylineGeometry, PolylineColorAppearance,
  ColorGeometryInstanceAttribute, Cartesian3, Color, ArcType } from "cesium";

scene.primitives.add(new Primitive({
  geometryInstances: new GeometryInstance({
    geometry: new PolylineGeometry({
      positions: Cartesian3.fromDegreesArray([0, 0, 5, 0]),
      width: 10.0,
      vertexFormat: PolylineColorAppearance.VERTEX_FORMAT,
      arcType: ArcType.GEODESIC, // GEODESIC, RHUMB, or NONE
    }),
    attributes: { color: ColorGeometryInstanceAttribute.fromColor(Color.WHITE) },
  }),
  appearance: new PolylineColorAppearance({ translucent: false }),
}));
```

## Enums

| Enum | Values | Used By |
|---|---|---|
| `ArcType` | `GEODESIC`, `RHUMB`, `NONE` | PolylineGeometry, PolygonGeometry |
| `CornerType` | `ROUNDED`, `MITERED`, `BEVELED` | CorridorGeometry, PolylineVolumeGeometry |
| `ClassificationType` | `TERRAIN`, `CESIUM_3D_TILE`, `BOTH` | GroundPrimitive, ClassificationPrimitive |
| `PrimitiveType` | `POINTS`, `LINES`, `TRIANGLES`, etc. | Low-level Geometry |
| `CloudType` | `CUMULUS` | CloudCollection |

## Camera Framing for Primitive Scenes

Programmatic checks (primitive count, camera position) can pass while the visual output completely misses the rendered geometry. A few rules of thumb that recur in visual evaluations:

- **Volume-geometry grids (cylinders/boxes):** use ~`-PI/4` (~`-0.785` rad, -45°) pitch from a range of `~3 * gridFootprint`. Pitches shallower than ~`-0.6` rad can park the grid behind the camera or out of frame; pitches near `-PI/2` flatten vertical extent and make the batch look like dots.
- **Surface polygons draped on terrain (GroundPrimitive):** with steeper pitches (~`-50°`) and far ranges, the polygon shrinks to a thin strip at the bottom edge. Reduce range or use a flatter pitch so the polygon occupies the centre of the frame.
- **Continental polylines (Route 66, transcontinental routes):** a near-nadir top-down view from ~5–6 Mm above the centroid keeps the full path visible.
- **Pin chains along a coast/route:** centre the camera on the midpoint of the chain at a range that covers the chain's bounding box with margin on all sides.

When in doubt, `viewer.flyTo(primitive)` or `viewer.camera.flyToBoundingSphere(primitive._boundingSpheres[0])` after `primitive.ready` will frame the batch automatically.

## Performance Tips

1. **Batch aggressively.** Combine thousands of GeometryInstances into one Primitive for a single draw call.
2. **Use `PerInstanceColorAppearance`** when each instance only needs a distinct color.
3. **Set `flat: true`** on PerInstanceColorAppearance when lighting is unneeded; uses `FLAT_VERTEX_FORMAT`.
4. **Set `allowPicking: false`** on Primitives that will never be picked to save GPU memory.
5. **Keep `asynchronous: true`** (default). Check `primitive.ready` before accessing instance attributes.
6. **Prefer fewer large collections** for Billboard, Label, and PointPrimitive. Group by update frequency.
7. **Use `BlendOption.OPAQUE`** on BillboardCollection/PointPrimitiveCollection when all items are opaque (up to 2x gain).
8. **Use buffer primitive collections** for large vector data when flyweight updates are acceptable.
9. **Precompute buffer collection bounding volumes** for large animated collections, but remember they are world-space in 1.142+.
10. **Use GroundPrimitive** for terrain draping instead of entity `heightReference`.
11. **Separate fill and outline** into two Primitives -- they cannot share a draw call.
12. **Match `vertexFormat` exactly** to the appearance to skip unused vertex attribute computation.
13. **Use `EllipsoidSurfaceAppearance`** over `MaterialAppearance` for surface geometry -- fewer vertex attributes.

## See Also

- **cesiumjs-entities** -- High-level Entity API wrapping primitives with time-dynamic properties.
- **cesiumjs-3d-tiles** -- Use `MVTDataProvider` for tiled vector data as runtime 3D Tiles.
- **cesiumjs-materials-shaders** -- Material (Fabric) system consumed by Appearances, post-processing.
- **cesiumjs-spatial-math** -- Cartesian3, Matrix4, Transforms, coordinate conversions for positioning geometry.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.