Skill v1.0.1
currentAutomated scan100/100Merge pull request #24 from gaopengbin/codex/fix-camera-target-framing
version: "1.0.1" name: cesiumjs-3d-tiles description: "CesiumJS 3D Tiles - Cesium3DTileset, compressed and CAD-style glTF content, MVTDataProvider, UrlTemplate3DTilesDataProvider, styling, metadata, feature picking, voxels, point clouds, I3S, Gaussian splats, clipping. Use when a task involves loading 3D Tiles or Mapbox Vector Tiles, draping vector tiles on terrain, rendering KHR meshopt/CAD content, styling or querying features, working with voxels or point clouds, or clipping spatial data."
CesiumJS 3D Tiles
Version baseline: CesiumJS v1.144 (ES module imports, async factory methods).
Loading a Tileset
Always use async factory methods -- never call the constructor directly. For public/no-token examples, prefer URL-backed tilesets such as CesiumGS sample tilesets. fromIonAssetId, createOsmBuildingsAsync, and Google Photorealistic 3D Tiles require external entitlements; use them only when the caller explicitly asks for those services and the runtime is configured for them.
import { Cesium3DTileset, HeadingPitchRange, Math as CesiumMath } from "cesium";// From a URLconst tileset = await Cesium3DTileset.fromUrl("https://example.com/tileset.json",{ maximumScreenSpaceError: 16 }, // lower = higher quality);viewer.scene.primitives.add(tileset);await viewer.zoomTo(tileset, new HeadingPitchRange(0.0, CesiumMath.toRadians(-25.0), tileset.boundingSphere.radius * 2.0,));
CesiumJS 1.143 applies standalone model loading to glTF embedded in tilesets. Read the glTF compatibility matrix for automatic KHR_meshopt_compression, CAD extension behavior, and the unsupported planar-fill boundary.
// From Cesium ionconst tileset = await Cesium3DTileset.fromIonAssetId(75343);viewer.scene.primitives.add(tileset);
// Google Photorealistic 3D Tilesimport { createGooglePhotorealistic3DTileset } from "cesium";const google3D = await createGooglePhotorealistic3DTileset({onlyUsingWithGoogleGeocoder: true,});viewer.scene.primitives.add(google3D);
// OSM Buildingsimport { createOsmBuildingsAsync } from "cesium";const osmBuildings = await createOsmBuildingsAsync();viewer.scene.primitives.add(osmBuildings);
Key Constructor Options
| Option | Default | Purpose | |
|---|---|---|---|
maximumScreenSpaceError | 16 | LOD quality threshold (pixels) | |
cacheBytes | 536870912 | Tile cache trim target (bytes) | |
maximumCacheOverflowBytes | 536870912 | Extra cache headroom | |
shadows | ShadowMode.ENABLED | Shadow casting/receiving | |
modelMatrix | Matrix4.IDENTITY | Root transform | |
clippingPlanes | undefined | ClippingPlaneCollection | |
clippingPolygons | undefined | ClippingPolygonCollection (WebGL 2) | |
enableCollision | false | Camera collision with tileset surface | |
pointCloudShading | undefined | Point attenuation options object | |
classificationType | undefined | TERRAIN, CESIUM_3D_TILE, or BOTH | |
dynamicScreenSpaceError | true | Horizon LOD optimization | |
foveatedScreenSpaceError | true | Center-screen tile priority | |
preloadFlightDestinations | true | Prefetch tiles at flight target | |
featureIdLabel | "featureId_0" | EXT_mesh_features ID set label | |
backFaceCulling | true | Cull back faces per glTF material | |
edgeDisplayMode | EdgeDisplayMode.SURFACES_ONLY | Render glTF edge-visibility data when present |
Mapbox Vector Tiles as Runtime 3D Tiles (Experimental, 1.142+)
MVTDataProvider loads {z}/{x}/{y} Mapbox Vector Tile .mvt/.pbf templates and converts tile payloads into runtime 3D Tiles. Use it when vector data is naturally tiled and you want 3D Tiles styling, metadata picking, and LOD instead of a single GeoJSON primitive.
For one in-memory or URL-backed GeoJSON object, prefer GeoJsonPrimitive in cesiumjs-primitives. For Entity/DataSource conveniences, prefer GeoJsonDataSource in cesiumjs-entities.
import {Cesium3DTileStyle,MVTDataProvider,Rectangle,} from "cesium";const provider = await MVTDataProvider.fromUrl("https://example.com/tiles/{z}/{x}/{y}.pbf",{minZoom: 4,maxZoom: 14,extent: Rectangle.fromDegrees(-125, 24, -66, 50),featureIdProperty: "id",},);viewer.scene.primitives.add(provider);// The provider owns a generated Cesium3DTileset.provider.tileset.style = new Cesium3DTileStyle({color: {conditions: [["${kind} === 'park'", "color('seagreen', 0.65)"],["${kind} === 'water'", "color('steelblue', 0.55)"],["true", "color('white', 0.45)"],],},});
Feature properties are encoded as EXT_structural_metadata, so standard 3D Tiles styling and picking patterns apply:
const picked = viewer.scene.pick(windowPosition);if (picked && typeof picked.getProperty === "function") {console.log(picked.getProperty("name"));}
Notes:
- URL templates must contain
{z},{x}, and{y}placeholders; tile URLs are parsed from/z/x/y. - Empty 204/404 tiles are treated as missing instead of hard failures.
provider.showproxies visibility to the generated tileset.- Runtime vector glTF content uses draft
EXT_mesh_polygonand3DTILES_content_gltf_vectorsupport; treat this path as experimental.
Terrain draping (1.144+): clamped vector tile polylines and polygons drape onto terrain automatically, with screen-space-constant line width, and per-feature styling stays driven by Cesium3DTileStyle. There is no opt-in flag; clamped vector content follows the terrain surface beneath it.
Custom vector tile formats (1.144+): MVTDataProvider now extends UrlTemplate3DTilesDataProvider, a public base class that turns any {z}/{x}/{y} URL-template vector source into a runtime-generated Cesium3DTileset. Its fromUrl, tileset, show, extent, and minZoom/maxZoom options behave the same as on MVTDataProvider; subclass it and implement its protected codec hook to support a tiled vector format other than MVT.
Tileset Events and Render Readiness
fromUrl resolves when tileset metadata is usable; it does not mean the tiles for the current camera view have rendered. initialTilesLoaded fires only for the first loaded view, while allTilesLoaded and tilesLoaded are view-dependent. After zoomTo, flyTo, setView, or interactive camera movement, check readiness again. Do not substitute a fixed delay for this semantic condition.
function waitForTilesetView(viewer, tileset, timeoutMs = 30_000) {return new Promise((resolve, reject) => {const scene = viewer.scene;let readyFrames = 0;const remove = scene.postRender.addEventListener(() => {readyFrames = tileset.tilesLoaded ? readyFrames + 1 : 0;if (readyFrames < 2) {scene.requestRender();return;}clearTimeout(timeoutId);remove();resolve(tileset);});const timeoutId = setTimeout(() => {remove();reject(new Error(`Tileset did not load within ${timeoutMs} ms`));}, timeoutMs);scene.requestRender();});}await viewer.zoomTo(tileset);await waitForTilesetView(viewer, tileset);
Use loadProgress for loading UI, tileLoad/tileUnload for cache activity, and tileFailed for diagnostics. Do not treat an individual tileLoad event as proof that the current view is complete.
import { Color } from "cesium";// Per-frame manual stylingtileset.tileVisible.addEventListener((tile) => {const content = tile.content;for (let i = 0; i < content.featuresLength; i++) {content.getFeature(i).color = Color.fromRandom();}});
Runtime Properties
import { Matrix4, Cartesian3 } from "cesium";tileset.show = false; // toggle visibilitytileset.maximumScreenSpaceError = 8; // increase qualityconst { center, radius } = tileset.boundingSphere;tileset.modelMatrix = Matrix4.fromTranslation(new Cartesian3(0, 0, 100));
Declarative Styling
Assign a Cesium3DTileStyle to tileset.style. Expressions reference feature properties with ${PropertyName}.
Style DSL constraints:
defined()is not supported in the style expression language; using it causes a render error.- Referencing a property that does not exist in the tileset data (e.g.,
${Height}on a tileset with no height attribute) halts style evaluation and triggers a Cesium error panel. Always guard with a["true", "..."]catch-all as the last condition. - To reset styles, assign
tileset.style = undefined.
import { Cesium3DTileStyle } from "cesium";// Color by height conditions -- requires tileset to have a 'Height' propertytileset.style = new Cesium3DTileStyle({color: {conditions: [["${Height} >= 100", "color('purple', 0.5)"],["${Height} >= 50", "color('red')"],["true", "color('blue')"], // catch-all: always include this],},show: "${Height} > 0",});
// Safe constant style -- works on any tileset regardless of metadatatileset.style = new Cesium3DTileStyle({color: {conditions: [["true", "color('cyan', 1.0)"],],},});
// Use defines to simplify repeated sub-expressionstileset.style = new Cesium3DTileStyle({defines: { material: "${feature['building:material']}" },color: {conditions: [["${material} === null", "color('white')"],["${material} === 'glass'", "color('skyblue', 0.5)"],["${material} === 'brick'", "color('indianred')"],["true", "color('white')"],],},});
// Show/hide by propertytileset.style = new Cesium3DTileStyle({show: "${feature['building']} === 'office'",});
// Point cloud stylingtileset.style = new Cesium3DTileStyle({color: "vec4(${Temperature})",pointSize: "${Temperature} * 2.0",});
tileset.style = undefined; // reset to default appearance
Color Blend Modes
import { Cesium3DTileColorBlendMode } from "cesium";tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE; // HIGHLIGHT | REPLACE | MIXtileset.colorBlendAmount = 0.5; // only used with MIX
Edge Display Mode (Experimental, 1.142+)
edgeDisplayMode controls edges contributed by the draft glTF EXT_mesh_primitive_edge_visibility extension. Tiles without that extension render normally regardless of this setting.
import { Cesium3DTileset, EdgeDisplayMode } from "cesium";const tileset = await Cesium3DTileset.fromUrl("/cad/tileset.json", {edgeDisplayMode: EdgeDisplayMode.SURFACES_AND_EDGES,});viewer.scene.primitives.add(tileset);// CAD-style wireframe for content that carries edge-visibility data.tileset.edgeDisplayMode = EdgeDisplayMode.EDGES_ONLY;// Default rendering: hide extension-provided edges.tileset.edgeDisplayMode = EdgeDisplayMode.SURFACES_ONLY;
Feature Picking and Properties
Scene.pick returns Cesium3DTileFeature for 3D Tiles features. Modifications persist until the owning tile is evicted from the cache.
import {ScreenSpaceEventHandler, ScreenSpaceEventType,Cesium3DTileFeature, Color,} from "cesium";const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);// Hover: read propertieshandler.setInputAction((movement) => {const feature = viewer.scene.pick(movement.endPosition);if (feature instanceof Cesium3DTileFeature) {const ids = feature.getPropertyIds();for (const id of ids) console.log(`${id}: ${feature.getProperty(id)}`);feature.color = Color.YELLOW; // highlight}}, ScreenSpaceEventType.MOUSE_MOVE);// Click: inspect a single propertyhandler.setInputAction((movement) => {const feature = viewer.scene.pick(movement.position);if (feature instanceof Cesium3DTileFeature) {console.log("Height:", feature.getProperty("Height"));feature.setProperty("selected", true); // write custom propertyfeature.show = false; // hide individual feature}}, ScreenSpaceEventType.LEFT_CLICK);
Inherited Metadata (3D Tiles 1.1 / EXT_structural_metadata)
// Searches: batch table -> content -> tile -> subtree -> group -> tilesetconst value = feature.getPropertyInherited("semanticOrPropertyName");
Clipping Planes
ClippingPlaneCollection clips via half-space planes in the tileset's local coordinate system.
import {ClippingPlane, ClippingPlaneCollection,Cartesian3, Color, Matrix4,} from "cesium";const clippingPlanes = new ClippingPlaneCollection({planes: [new ClippingPlane(new Cartesian3(0.0, 0.0, -1.0), 0.0)],edgeWidth: 1.0,edgeColor: Color.WHITE,unionClippingRegions: false, // false = intersection (AND); true = union (OR)});const tileset = await Cesium3DTileset.fromUrl(url, { clippingPlanes });// Or: tileset.clippingPlanes = clippingPlanes;// Offset the clip boundary at runtimeclippingPlanes.modelMatrix = Matrix4.fromTranslation(new Cartesian3(0, 0, 50));clippingPlanes.get(0).distance = 25.0;
Clipping Polygons
ClippingPolygonCollection clips using arbitrary polygons. WebGL 2 only.
import { ClippingPolygon, ClippingPolygonCollection, Cartesian3 } from "cesium";const polygon = new ClippingPolygon({positions: Cartesian3.fromDegreesArray([-105.0077, 39.7519, -105.0095, 39.7504,-105.0071, 39.7513, -105.0077, 39.7519,]),});tileset.clippingPolygons = new ClippingPolygonCollection({polygons: [polygon],inverse: false, // false = clip inside polygon; true = clip outside});// Also works on the globeviewer.scene.globe.clippingPolygons = new ClippingPolygonCollection({polygons: [polygon],});
Point Cloud Shading
const tileset = await Cesium3DTileset.fromUrl(pointCloudUrl, {pointCloudShading: {attenuation: true, // scale points by geometric errorgeometricErrorScale: 1.0,maximumAttenuation: 10, // max pixel size; undefined = maximumScreenSpaceErroreyeDomeLighting: true, // depth-aware edge enhancementeyeDomeLightingStrength: 1.0,eyeDomeLightingRadius: 1.0,backFaceCulling: false, // requires normals in point datanormalShading: true,},});viewer.scene.primitives.add(tileset);// Runtime adjustmenttileset.pointCloudShading.eyeDomeLightingStrength = 2.0;
Voxel Primitives
VoxelPrimitive renders volumetric data from a Cesium3DTilesVoxelProvider. Shapes: BOX, CYLINDER, ELLIPSOID (see VoxelShapeType).
import {VoxelPrimitive, Cesium3DTilesVoxelProvider,CustomShader, viewerVoxelInspectorMixin,} from "cesium";const provider = await Cesium3DTilesVoxelProvider.fromUrl("voxel/tileset.json");const voxelPrimitive = new VoxelPrimitive({provider,customShader: new CustomShader({fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {material.diffuse = fsInput.metadata.a.rgb;material.alpha = fsInput.metadata.a.a;}`,}),});viewer.scene.primitives.add(voxelPrimitive);voxelPrimitive.nearestSampling = true;viewer.camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, { duration: 0 });// For voxel shader authoring — struct availability, raymarching semantics, metadata// access — see the cesiumjs-custom-shader skill. This skill covers VoxelPrimitive setup.// Optional inspector widgetviewer.extend(viewerVoxelInspectorMixin);viewer.voxelInspector.viewModel.voxelPrimitive = voxelPrimitive;
I3S Data Provider
Load Esri I3S scene layers (3D Objects, IntegratedMesh, Building Scene Layer).
import { I3SDataProvider, ArcGISTiledElevationTerrainProvider, Ellipsoid, Rectangle } from "cesium";const geoidService = await ArcGISTiledElevationTerrainProvider.fromUrl("https://tiles.arcgis.com/tiles/.../EGM2008/ImageServer",);const i3sProvider = await I3SDataProvider.fromUrl("https://tiles.arcgis.com/tiles/.../SceneServer/layers/0",{ geoidTiledTerrainProvider: geoidService },);viewer.scene.primitives.add(i3sProvider);const center = Rectangle.center(i3sProvider.extent);center.height = 5000.0;viewer.camera.setView({destination: Ellipsoid.WGS84.cartographicToCartesian(center),});
Gaussian Splats
Loaded as standard 3D Tiles; CesiumJS handles KHR_gaussian_splatting automatically.
const splats = await Cesium3DTileset.fromIonAssetId(3667783);viewer.scene.primitives.add(splats);viewer.zoomTo(splats);
Classification
Drape tileset geometry as a classification overlay on terrain or other tilesets.
import { Cesium3DTileset, ClassificationType } from "cesium";const classified = await Cesium3DTileset.fromUrl(url, {classificationType: ClassificationType.BOTH, // TERRAIN | CESIUM_3D_TILE | BOTH});viewer.scene.primitives.add(classified);
Adjusting Tileset Height
import { Cartographic, Cartesian3, Matrix4 } from "cesium";const cartographic = Cartographic.fromCartesian(tileset.boundingSphere.center);const surface = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);const offset = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);const translation = Cartesian3.subtract(offset, surface, new Cartesian3());tileset.modelMatrix = Matrix4.fromTranslation(translation);
Performance Tips
- Keep
maximumScreenSpaceErroras high as acceptable (16 default; 32+ for mobile). - Leave
dynamicScreenSpaceError: truefor street-level views with large tilesets. - Leave
foveatedScreenSpaceError: trueto prioritize center-screen tiles. - Size
cacheBytesandmaximumCacheOverflowBytesto device memory (512 MB each default). - Use
preloadFlightDestinations: trueto prefetch tiles at the camera flight target. - Enable
skipLevelOfDetail: truefor large replacement-refined tilesets to reduce memory. - Avoid
maximumScreenSpaceErrorbelow 4 -- diminishing returns, many more tile requests. - For point clouds, enable
attenuationandeyeDomeLightingto fill gaps and add depth. - Keep
enableCollision: falseunless camera collision or CLAMP_TO_GROUND on tiles is needed. - Preload hidden tilesets with
show: falseandpreloadWhenHidden: true. - Avoid translucent styles when possible -- they add rendering passes and disable optimizations.
- Listen to
tileFailedto log errors; calltrimLoadedTiles()after large camera jumps.
See Also
- cesiumjs-models-particles -- glTF compression and CAD-extension compatibility used by tile content
- cesiumjs-custom-shader -- GLSL authoring for
Cesium3DTileset.customShaderandVoxelPrimitive.customShader(struct reference, feature IDs, metadata) - cesiumjs-materials-shaders -- ImageBasedLighting, post-processing stages for tilesets
- cesiumjs-interaction -- Scene.pick, drillPick, ScreenSpaceEventHandler for feature selection
- cesiumjs-terrain-environment -- Globe, terrain providers, atmosphere, lighting, shadows