Releases: WorldWindEarth/WorldWindKotlin
Release list
WorldWind Kotlin v2.0.6
WorldWind Kotlin 2.0.6
This release rebuilds the real-time visibility stack: cascaded sun shadows and sightline analysis are rewritten on depth-texture pipelines with PCF filtering — moment shadow maps are gone from the engine — with hardware depth-compare samplers on capable GPUs, off-screen shadow casters, alpha-cutout shadows, and hemispheric ambient shading. Alongside: a 3D Tiles texture-upload throttle, a batch of iOS stability fixes, and MVT vector layer-ordering and offline-cache fixes.
🌑 Shadow pipeline rewritten as depth-texture CSM
- Depth-only cascade maps with tent PCF replace the previous moment-based cascades. Receiver shadow math is camera-relative and caster matrices compose in double precision — raw ECEF float32 quantizes to ~0.5 m and erased street-scale shadow structure. Cascades fit the accumulated scene bounds with sticky hysteresis and a texel-snapped light anchor, so the maps stay bit-stable under camera motion. Casters rasterize double-sided (photogrammetry winding is unreliable), and terrain casters skip 3D-Tile coverage regions so the coarse elevation surface can't blanket photogrammetry in false shadow.
ShadowLayer.debugShadowModeadds cascade-band / footprint / map-depth debug views. - Off-screen casters keep casting — view-frustum-culled shapes, COLLADA/glTF models, and 3D tiles that intersect the street-scale cascade regions still rasterize into the shadow maps (previously their shadows popped in and out as the camera turned). 3D tiles refine against a coarser screen-space-error budget for shadow-only selection, and all keepalive content skips the color pass. Tunable via
ShadowLayer.offscreenCasterCascades(default 2, 0 disables). - Alpha-masked casters cast their cutout silhouette — cutout-textured 3D-Tile submeshes (alphaMode MASK) discard transparent fragments in the depth pass instead of casting full-quad slab shadows.
- Shadows fade across the terminator instead of vanishing in a single frame at sunset; sun elevation is judged against the shaded content's position, so an orbital camera keeps shadows on sunlit terrain viewed across the limb.
- Casters sunward of a cascade's near plane are pancaked onto it, so a distant mountain toward a low sun still shadows street-scale cascades instead of being clipped out of the depth map.
- Elevated casters gate per cascade — a single model at altitude no longer inflates every cascade's depth window (which cost depth precision and detached contact shadows scene-wide); only the cascades its light-space footprint overlaps are lifted.
- Receiver depth bias is now in world metres, normalized per cascade at upload, so bias tuning means the same thing in every scene regardless of depth-window size.
- Two receiver safeties: tap depths clamp to [0, 1] (fixes false dark speckle on far-cascade valley floors at grazing sun on the software path), and the shadow distance cap uploads the fitted coverage end so a hard cutoff line can never appear inside populated cascade coverage.
🔭 Sightline rewritten on a depth cube
- One depth-only cube map serves all sightlines, resolved by tent PCF with receiver-plane depth prediction (cures grazing-pavement acne for street-level observers). All six faces render, so receivers above the observer — city-canyon facades — tint correctly. Terrain tiles inside 3D-Tile coverage are skipped as occluders, matching the shadow pass.
DirectionalSightlineandOmnidirectionalSightlinemerge intoRealtimeSightline(breaking) — the cube always covers the full sphere, so direction is just a receiver-side mask:fieldOfViewnow spans (0°, 360°], where 360° is the former omnidirectional and the old 90° limit is gone. Named against its static-analysis peerViewshedSightline.- The depth-cube pass is range-culled — terrain tiles and occluders whose bounds miss the observer's range sphere skip all six faces. Previously a street-scale observer rasterized the whole visible terrain set six times; the cube also survives pans that used to invalidate it.
- Multi-window safety: the sightline receiver scratch matrix moved onto the per-window draw context, and the depth-cube bind cache is invalidated whenever a benign cube is parked, so receiver order can never sample the wrong cube.
📐 Viewshed fixes
- The GPU ray-walk's constant step cap silently painted every cell beyond it visible; a rectangular AOI with the observer near a corner reported false coverage across its far region. The cap is now exact per dispatch, and an exhausted walk reads occluded — the conservative default.
- The draft → full-resolution refine could stall forever on event-driven platforms (JVM/Android) when a draft dispatch spanned more than one frame; the frame pump now keeps running while escalation work is pending.
⚡ Hardware depth-compare receivers
- Shadow and sightline receivers compile as GLES 3 / WebGL 2 / GL 3.3 core with
sampler2DShadow/samplerCubeShadowwhere the platform supports them: nine hardware 2×2-PCF taps replace the 16-fetch software cascade filter, and each cube tent tap becomes one hardware compare. GLSL ES 1.00 in-shader compares remain the WebGL1 fallback, and Adreno GPUs stay on the software path (hardware compare is driver-unpredictable there). Also fixes the depth-read quantization artifacts (zebra stripes/circles) on Mac "Designed for iPad" builds.
💡 Shading
- Hemispheric ambient for lit shapes, meshes and 3D tiles: the flat 0.35 ambient becomes a sky/ground hemisphere term with the same average — up-facing surfaces read sky-lit, undersides ground-lit, vertical walls unchanged.
🚀 Performance
- 3D Tiles: byte-budgeted texture uploads — city-scale loading eager-bound every freshly-installed texture in one frame (a single 2048² mipmapped texture is ~21 MB of
glTexImage2D), producing 20–150 ms GL-thread stalls. Uploads now drain in per-frame batches capped by measured upload throughput, targeting a 3 ms bind slice. - Android: no more double draw — every discrete redraw and every pick used to cost a second full-scene draw of identical content; the follow-up GL pass is now requested only while the pick or frame queue still holds work.
🍏 iOS fixes
SynchronizedPoolwas a plain unsynchronized pool despite the name; tile-decode workers and the render thread share the engine's pools, so concurrent access could corrupt them or hand the same instance to two consumers. Now locked, mirroring the JVM/Android contract.- 3D Tiles child-URI resolution collapses
../dot segments — servers 403 raw dot segments sent verbatim by the Darwin HTTP stack, which spun tile fetching in an endless cancel/re-auth loop. - The render-resource cache sizes to physical memory like Android; the fixed 512 MB cap caused cyclic texture eviction on horizon views (whole-frame flicker and transient geometry corruption).
- The native Draco decoder is enabled in the Xcode run script (the no-op stub decoded Draco tiles to empty geometry), and the Kotlin framework is declared as a script-phase output so the app relinks instead of shipping a stale build.
- A cached feature source that yields zero rows refetches from origin, and an undecodable persisted feature file is deleted instead of leaving the layer blank forever.
🗺 Vector layers & MVT
- Layer order now wins for draped shapes — MVT content rendered above imagery and user surface shapes regardless of layer order because it enqueued at elevated z bands; all MVT draped content now sits at the shared surface z, matching the insertion-order model of the reference engines.
- Data-availability sector persisted —
VectorLayer.sector(mirroringElevationCoverage.sector) gates tile assembly; bulk downloads union the region into it, it persists as the GeoPackage content bbox, and reopening a cache restores it. - Request headers persist with the cache — a reopened cached MVT layer keeps e.g. the
User-AgentOSM's tile policy requires. - No-data tiles stay transparent instead of painting an opaque style-background sheet over underlying layers.
- Default styles aligned with live reference tile schemas: road
_linkramps, OMTpath/trackclasses,rock/wetlandlandcover, OpenTopoMap cream background, and no more debug-gray fill on unmapped layers.
🐞 Fixes
- SLPK: texture names are resolved from I3S metadata (
textureSetDefinitions, or the legacy per-node href) instead of a hardcoded0.jpg— fixes white untextured meshes on Metashape-produced packages. - GeoPackage: a cancelled reader can no longer leak the read-handle pool empty.
📚 Tutorials
- The 3D Tiles Archive (
.3tz/.3dtiles) tutorial now runs on Android too, sharing one implementation with JVM.
📦 Dependency updates
- mil-sym 2.11.2 (Android) / 2.8.2 (Java) / 2.9.2 (TS), xmlutil 1.0.1.
Full changelog: v2.0.5...v2.0.6
WorldWind Kotlin v2.0.5
WorldWind Kotlin 2.0.5
This release adds three new local data formats (ArcGIS SLPK/I3S scene layers, Cesium 3D Tiles archives, and LAS/LAZ point clouds), extends offline bulk caching to vector layers, and delivers a round of performance and stability fixes for tiled vector rendering — alongside a large internal consolidation pass that removes ~1,900 lines of duplicated code with no behavior change.
✨ New format support
- ArcGIS SLPK (I3S) scene layers — render an Esri Scene Layer Package (I3S 1.7+ integrated mesh) in place from a local
.slpk, with no extraction or copy. Reuses the existing 3D Tiles streaming, traversal, cache, and GPU pipeline through a byte-source seam. Seam-free across tiles, textured, withaltitudeOffsetsupport. Validated on real Esri IntegratedMesh v1.8 datasets. (JVM + Android) - Cesium 3D Tiles archives (
.3tz/.3dtiles) — open a local 3D Tiles archive directly on the globe withopenArchived3dTilesLayer(file). Magic-byte sniffing picks the reader:.3tz(ZIP) or.3dtiles(SQLitemediatable). No decoder or layer subclass needed — the archive holds ordinary 3D Tiles content. (JVM + Android) - LAS/LAZ point clouds — new
PointCloudLayerloads a standalone.las/.lazfile onto the globe, reusing the.pntsGPU path. Pure-Kotlin LAS reader (point formats 0–3, 6–8) and LASzip decoder (chunked formats 0–3), verified byte-identical against thelaz-rsreference. Auto-georeferences WGS84/NAD83/ETRS89 UTM and geographic CRS, geoid-aware altitude so orthometric LiDAR rests on terrain, livealtitudeOffset, and progressive off-thread loading that keeps single-threaded targets (JS/wasm) interactive.
📦 Offline bulk caching for vector layers
- Bulk region download for MVT and OSM Buildings — the "save a region for offline use" flow (previously WMS/WMTS raster and elevation only) now covers MVT vector tiles and OSM Buildings, writing through to the same GeoPackage / IndexedDB / file stores. A resolution range picks the same slippy zoom levels as the raster/elevation path.
tileCount()on MVT and OSM Buildings layers, mirroringLevelSet.tileCount, so a download can be sized before committing to it.clone()promoted to theVectorLayercontract, implemented by all four vector layers — each builds a fresh instance from config only, never sharing live scopes, semaphores, or caches.
🚀 Performance & stability
- Bounded GPU memory for tiled feature layers — the resident-tile LRU can now be bounded by real VBO/EBO bytes instead of a flat tile count, so a few dense full-res WFS tiles can no longer balloon GPU memory until the Adreno KGSL driver fails to
mmap.TiledFeatureLayerdefaults to a 96 MB budget. - Per-frame upload throttle — a pan's burst of freshly-fetched tiles is now admitted a few at a time instead of all in one frame, spreading their GPU buffer uploads across frames and eliminating an observed ~410 ms GL-thread hitch.
- MVT assembly parallelism capped — the background assembly pool no longer sizes to every core; it now leaves cores free for the render/GC threads. Measured on an S23: worst-case frame 568 → 335 ms,
GL.draw492 → 201 ms. - WFS refresh efficiency —
WfsLayernow caches the GetCapabilities document and reuses a single HTTP client across refreshes, so an auto-refreshing viewport no longer re-downloads capabilities or rebuilds an HTTP client on every pan. The fetch loop is also split from renderable decode, dropping a per-page renderable graph that was built and immediately discarded.
🐞 Fixes
- Web:
serviceTypeToDataTypewas missing "WCS 2.0.1", registering WCS coverages as tiles on JS/wasm. - iOS:
FileSystemElevationBackend.writeTilenever deleted the ancillary sidecar on default packing, mis-decoding a re-packed tile. - Web:
ContentManager.findEntryhard-codedlastModified = nullwhilelistEntriespopulated it. - Non-casting 3D-tile meshes are now correctly excluded from the shadow cascade cull.
- JVM: a fast click with incidental jitter no longer triggers an unwanted inertial fling — the gesture must now move beyond the same 5 dp slop the minimap uses, scaled by display density.
🧹 Internal / maintainability
A behavior-preserving consolidation pass across independent subsystems (verified compiling on JVM/JS/wasmJs/iOS, with jvmTest passing):
- All eight 2D map projections now share their grid/border tessellation loops (−609 lines), backed by a new consistency test covering every projection.
- Web-Mercator / slippy tile math consolidated into a single
SlippyTilessource of truth (tile conversions,MercatorSectordelegation, fractional unprojection). - Shared cores extracted for: tile-fetch exponential backoff, bulk-retrieval retry/backoff, iOS/Web elevation-backend delegation, shadow/sightline receiver uniform plumbing (−231 lines), the MSM shadow GLSL reconstruction, glTF accessor readers, moments blur, and the lazy HTTP client pattern.
- Five latent bugs fixed while de-duplicating tile-key and slippy math (see Fixes above).
Tutorials
- New tutorials wired across JVM / web / iOS / Android shells: Point Cloud (LAS/LAZ), ArcGIS SLPK (I3S), and 3D Tiles Archive.
- Tutorial lists reordered into themed groups progressing from basics to complex format rendering, consistently across all platforms; the Android drawer gets a distinct icon per theme.
Full changelog: v2.0.4...v2.0.5
WorldWind Kotlin v2.0.4
Release 2.0.4
43 commits since 2.0.3.
3D Tiles
- Render KTX2 (
KHR_texture_basisu) textures instead of dropping them. - Enable glb-wrapped Gaussian splats out of the box.
- Throttle Gaussian-splat parses inside the layer, and move the Gaussian view-Z sort to
Dispatchers.Defaultto keep it off the render thread. - Depth-pick now lands on the Gaussian-splat surface.
- Expose the root bounding-volume sector for fly-to.
- Priority-ordered fetch queue with off-permit cache writes.
- Evict cold
.json-grafted subtrees to bound theTile3dnode count. - Combine vbo + ebo + batchIds into one
BufferObjectper primitive; cache hot-path render-resource-cache refs inMeshSubmesh; inline traversal distance onTile3dand drop thesortDistancesHashMap. - GL-thread feedback for the adaptive install throttle.
- Stencil-mask terrain inside mesh / point-cloud coverage so terrain no longer pokes through.
- Mesh tiles cast into the sightline moments cube, and always pick the sightline-spliced program variant.
- Reset the altitude-bake cache when a tileset drops on auth-expired; create the blob-store table on NGA's handle and fix the per-class DAO cache; report cached blob-store size from
sizeBytesOf.
WFS
- Viewport auto-refresh with a GeoPackage cache (download-once & tiled).
- Retry transient fetch failures with linear backoff.
MVT
- Shared
TiledVectorLayerbase, earcut polygon fills, progressive cut, and configurable background + label density.
GeoPackage
- Read-only handle pool for concurrent feature reads.
- Add
deleteFeatureTableand narrow the features bbox to the actual extent.
Sightline
- Bind benign textures on the no-sightline receiver path.
- Clamp the moments texture wrap to edge.
- Identity-keyed receiver cache; drop
frameStamp.
Rendering & Performance
- WebGL: per-target buffers for 3D Tiles and DPI-correct curved labels.
- Cache: stale-while-revalidate on the cached-tile fast path, with discard-on-cancel.
- Elevation: keep cache decode and miss-retry off the render thread.
RenderResourceCache: per-frame eviction time budget, and the Web path is now implemented.BasicTessellator: stableBufferObjectkeys with a version bump on re-prepare.- Shapes: one-primitive outlines and exact (non-MSAA) surface-shape picking.
- Placemark/Label: claim near-plane depth and discard transparent fragments; adaptive Label depth offset.
Texture: caller opt-out for mipmap generation.Drawables: reference-only resets.- OSM Buildings: persist render config in the cache and filter redundant outlines.
Fixes & Internals
- WMS capabilities: treat Layer attributes (
queryable,cascaded,opaque,noSubsets,fixedWidth,fixedHeight) as unqualified per the XML standard, fixing parsing under xmlutil 1.0.0. HttpClient: installsocketTimeoutMillison all platforms.- Tutorials (JVM): export
sun.awtto silence JOGLAppContextInfospam. - Fix unresolvable Dokka KDoc links.
Build & Dependencies
compileSdk/targetSdkraised to 37 (required by lifecycle-compose 2.11.0).- lifecycle-compose
2.10.0→2.11.0 - ktor
3.5.0→3.5.1 - xmlutil
0.91.3→1.0.0 - mil-sym (android
2.11.0→2.11.1, java2.8.0→2.8.1, ts2.9.0→2.9.1) - javafx
21.0.5→26.0.1 - webp-imageio
0.10.2→0.11.0
WorldWind Kotlin v2.0.3
WorldWind Kotlin 2.0.3
11 commits since 2.0.2.
3D Tiles (new)
OGC / Cesium 3D Tiles 1.0 / 1.1 streaming layer. Supports b3dm / i3dm / cmpt / pnts / glTF binary / Gaussian-splat payloads, served via Cesium Ion + Google Photorealistic + Bearer / custom-headers auth providers, with per-feature picking through batch tables. Vertical-datum correction via altitudeOffset + altitudeMode (ABSOLUTE / RELATIVE_TO_GROUND / CLAMP_TO_GROUND). Stencil-masked skip-LoD keeps fallback tiles visible under refining children without inter-LoD flicker.
Niantic SPZ Gaussian-splat codec wired in via the gaussianLoader hook on Ogc3dTilesLayer.
Tutorials: OGC Container (Utrecht), Google Photorealistic, Cesium Ion (defaults to asset 96188 OSM Buildings; swappable to any Ion asset).
Codec modules — new, independent Maven coordinates at 1.0.0
Three optional native-backed glTF extension modules. Each ships per-platform binaries (JVM JARs for macos-aarch64 / macos-x86_64 / linux-x86_64 / windows-x86_64; Android AARs for all four ABIs; iOS klibs via cinterop) and registers itself with the engine at startup via the corresponding installXxxDecoder() call.
| Module | glTF extension(s) covered |
|---|---|
earth.worldwind:worldwind-formats-gltf-draco:1.0.0 |
KHR_draco_mesh_compression + 3DTILES_draco_point_compression |
earth.worldwind:worldwind-formats-gltf-ktx2:1.0.0 |
KHR_texture_basisu (Basis Universal) |
earth.worldwind:worldwind-formats-gltf-meshopt:1.0.0 |
EXT_meshopt_compression |
The engine has no compile-time dependency on these — add them to your build only if your tilesets use the matching extension.
Fixes
- Web — EGM96 geoid byte-order. The Kotlin/JS actual was viewing the fetched binary as a platform-native
Int16Array(little-endian on x86/ARM) while the NGA file is big-endian per spec, returning byte-swapped offsets. Terrain mesh drifted by the local geoid separation: Utrecht tilesets levitated ~44 m above terrain, Manhattan OSM Buildings sat ~34 m below it. Fixed inwebMain/EGM96Geoid.kt.
Other changes
- JVM rendering:
WorldWindowswapped fromGLJPanelto heavyweightGLCanvas. Fewer blit copies, no PBO contention with Swing's repaint, far better per-frame stability under continuous redraws. - GeoPackage caches: per-put LRU eviction across all stores; long-running sessions can no longer out-grow their byte budget.
CachePolicy: removed themaxBytescapacity cap. LRU eviction pressure makes the hard ceiling redundant; callers size caches by working-set instead.- Internal: project version externalised to
gradle.properties(worldwind.version,worldwind.codecsVersion) for CI-driven auto-release on master pushes when either advances past Maven Central.
Web tutorial host (GitHub Pages)
The Pages tutorial bundle now reads CESIUM_ION_TOKEN and GOOGLE_MAPS_API_KEY from repository Secrets at build time. Keys ship inside the public JS bundle, so they must be locked at the provider — HTTP-referrer restriction on the Google Maps Platform key (Map Tiles API only), asset-scoped + referrer-allowlisted Cesium Ion token (assets:read only). Placeholders fall back at build time for local dev without secrets configured.
WorldWind Kotlin v2.0.2
WorldWind Kotlin 2.0.2
A maintenance release focused on local elevation coverages, terrain-cache correctness, and a few rendering fixes. 10 commits since 2.0.1.
dependencies {
implementation("earth.worldwind:worldwind:2.0.2")
}Highlights
Elevation
- Local DTED elevation coverage. New
DtedElevationCoverageserves a local.dt0/.dt1/.dt2directory as a single tiled coverage: one pyramid and one cache that resolve the finest level present per 1° cell (dt2 > dt1 > dt0, no duplicated data). Coarse cells upsample smoothly via windowed bilinear reads on the IO dispatcher, zoomed-out grids sparse-sample a cached per-cell overview, and height limits come from the base per-tile min/max — no synchronous reads on the render thread. - Cheaper height limits. Per-tile elevation min/max is cached, skipping the render-thread texel rescan and the associated per-frame work.
- Terrain now reacts to coverage changes.
ElevationModeladvances its timestamp on every structural change (add / remove / clear / invalidate), not just on data updates. Disabling or removing a coverage now reliably re-tessellates the terrain instead of leaving stale, terraced heights behind.
Rendering & shapes
- Fixed placemark leader lines turning to noise when drawn over lit shapes.
- The
Labeldepth offset is now configurable (default-0.003). SelectDragDetectornow drags relative-to-ground and above-sea-level shapes consistently with absolute-altitude shapes.
GeoPackage
GpkgContentManagerdefers cache eviction off the store-open path, so opening a store no longer blocks on eviction.
Web / MIL-STD-2525
- MIL-STD-2525 modifiers/attributes are optional again on the web (js / wasmJs) targets.
Internals
Angle.fromDMSprecompiles its DMS regexes into constants.- Silenced Dokka unresolved-link and tutorial compiler warnings.
Full changelog
ee3d8c92ElevationModel: invalidate terrain on coverage add/remove/clear00e63f0dGpkgContentManager: defer cache eviction off the store-open path00e7b07aDtedElevationCoverage: single best-level local DTED elevation coverage10438b2eAngle.fromDMS: precompile DMS regexes into constantsebe1e0ebFix placemark leader lines turning to noise over lit shapes35bbfafaCache per-tile elevation min/max; skip texel rescan for height limits72d74e9dSelectDragDetector: drag relative-to-ground and above-sea-level like absolute36189a0dMake Label depth offset configurable, default -0.0038b3e3510Make MIL-STD-2525 modifiers/attributes optional again on web targetsb80b734eSilence Dokka unresolved-link and tutorial compiler warnings
Full Changelog: 2.0.1...2.0.2
WorldWind Kotlin v2.0.1
WorldWind Kotlin 2.0.1
23 commits since 2.0.0.
Maintenance and platform release. Headline items: a new Kotlin/Wasm (wasmJs) browser target across every module, a reworked stale-while-revalidate caching layer with capacity eviction on all platforms, a flat, interoperable GeoPackage feature cache, and low-memory streaming COLLADA/KMZ loading for large photogrammetry.
implementation 'earth.worldwind:worldwind:2.0.1'🌐 Kotlin/Wasm (wasmJs) browser target
- New
wasmJstarget across all modules (library, Compose, tutorials, samples). Browser code is shared betweenjsandwasmJsin awebMainsource set (~53 files) behindexpect/actualseams:WebKgl/createWebKglfor the WebGL backend andMilStd2525Rendererfor the mil-sym-ts / canvg bindings; only the generated externs, the per-target renderer adapter, andWeakRefstay per-target. kotlinx-browserreplaces the kotlin-wrappers BOM; GeoJSON swapped to the wasm-capable spatialk library;pngjmoved tojvmCommon.- Source sets modernized via
applyDefaultHierarchyTemplate(nonIos→web {js, wasmJs}+jvmCommon {jvm, android}). - Web tutorials / compose-samples dev-server ports pinned (js + wasmJs) so the browser origin — and the origin-scoped IndexedDB cache — stays stable across runs.
💾 Cache: stale-while-revalidate + capacity eviction
- Tile freshness moved to a side table; effective SWR for image, elevation, and vector tiles.
- Conditional-GET (ETag / Last-Modified) revalidation for the elevation cache on all platforms.
- SWR for feature tiles with age-safe eviction; JS/iOS feature-store SWR freshness.
- Capacity eviction (
maxBytes) for the JS/iOS tile stores and the GeoPackage tile store. - Strict local/remote separation for image + elevation retrieval.
CacheEvictionPolicyrenamed toCachePolicy(type + attributes). (API change)
🗺️ GeoPackage / features
- Flat, two-way-interoperable GeoPackage feature cache.
gpkg_web_servicerenamed toww_web_servicewith automatic migration. (schema change)- Drop upstream-vanished features on re-fetch; render a boundary-straddling feature in exactly one tile.
🧊 COLLADA / KMZ (large photogrammetry)
- Low-memory streaming
.daeparse (custom pull tokenizer — never materializes the document or numeric bodies as strings), a configurable texture-size cap (default 2048; an 8192² atlas → 16 MB instead of 256 MB decoded), and zip-direct KMZ loading (no extraction to disk; textures decoded lazily and LRU-evicted). - KMZ models are anchored from the
doc.kml<Model>placement — position, scale, orientation, and<altitudeMode>(including thegx:sea-floor extensions). - Terrain-relative altitude modes (
CLAMP_TO_GROUND/RELATIVE_TO_GROUND) place the model on the resolved terrain and follow it as elevation streams in (rebuild keyed on vertical exaggeration / elevation timestamp / globe state). Y_UPmodels follow the KML model-facing convention (heading 0 → North-aligned) so geo-referenced KMZ tiles (e.g. OpenDroneMap) align out of the box; the COLLADA up-axis alone gives no geographic facing. Cosmetic for bare, manually-placed.dae(override withzRotation).
🛠️ Fixes & internals
- Reuse a single HTTP client per feature source (+ per-client tutorial SSL).
- WCS tutorial loads terrain once across all platforms.
- Gesture: skip pan frames with a non-finite meters-per-pixel.
- WGS coordinate parsing fix.
- e2e reopen tests for tile/coverage caches + NGA reference-validator pass.
- Remove dead
getFeatureListcode and the dormant JSWebTileCache; extract inlined FQN type references to imports.
Commits
- Add Kotlin/Wasm (wasmJs) browser target across all modules
- Deps: swap GeoJSON to wasm-capable spatialk; move pngj to jvmCommon
- Cleanup: extract inlined FQN type references to imports
- COLLADA: streaming parse, texture cap, and zip-direct KMZ loading
- Networking: reuse one HTTP client per feature source; per-client tutorial SSL
- WCS tutorial: load terrain once across all platforms
- Gesture: skip pan frames with a non-finite meters-per-pixel
- Cache: strict local/remote separation for image + elevation retrieval
- Cache: capacity eviction for the JS/iOS tile stores
- Coverage: conditional-GET revalidation for the elevation cache (all platforms)
- Cache: JS/iOS feature-store SWR freshness + capacity eviction
- Cache: rename CacheEvictionPolicy -> CachePolicy (type + attributes)
- Tests: e2e reopen for tile/coverage caches + NGA reference-validator pass
- GeoPackage: rename gpkg_web_service -> ww_web_service with migration
- Features: drop upstream-vanished features on re-fetch (GeoPackage)
- Features: render a boundary-straddling feature in exactly one tile
- JS: remove the dormant WebTileCache
- Cache: implement maxBytes for the GPKG tile store
- Features: flat, two-way-interoperable GeoPackage feature cache
- Cache: move tile freshness to side table + effective SWR for image/elevation/vector
- Fix WGS coordinates parsing
- Remove dead getFeatureList code
- Cache: stale-while-revalidate for feature tiles + age-safe eviction
Full changelog: v2.0.0...v2.0.1
WorldWind Kotlin v2.0.0
WorldWind Kotlin 2.0.0
Major release — 91 commits since 1.14.2. New vector/feature/imagery layers, a
ground-up cache redesign, and broad rendering and API improvements.
Warning
This is a major release with breaking API and package changes. See
Breaking changes before upgrading.
Breaking changes
- Cache API replaced (source-decorator design). The layer-typed
CacheableXxx
interfaces (configureCache/disableCache/cacheTileFactory/
isCacheConfigured) are gone. Caching is now external:attachCache(layer, contentKey)
wraps the layer's source in aCachedTileSource/CachedBulkFeatureSource/
CachedTiledFeatureSourcedecorator, andlistEntries()+openLayer<T>(contentKey)/
openElevationCoverage<T>drive cross-launch reopen. - GeoPackage moved packages:
earth.worldwind.ogc.gpkg/earth.worldwind.ogc.Gpkg*
→earth.worldwind.formats.gpkg. GPKG is a file format, not a network service, so
ogc/now contains only network protocols. Update imports accordingly. GeoJsonLayerFactoryis now a thin facade overGeoJsonBulkFeatureSource+
BulkFeatureLayer; the oldexpect/actual extractGeoJsonPropertiesAPI was removed.SelectDragCallbacknow takesAnyinstead ofRenderable.- Compose Multiplatform 1.11.0 drops the
iosX64target in the-compose/
-compose-samplesmodules.
New layers
- MVT (Mapbox Vector Tile) layer with full Mapbox-GL parity: expression DSL
(case/match/step/interpolate/bezier/feature-state/rgb/
rgba/geometry-type/line-progress), per-feature picking, sprite atlases,
per-glyph curved-baseline text,line-gradient,fill-extrusion-height(3D buildings),
line-dasharray+ casings,icon-text-fithighway shields,text-max-widthword wrap,
schema auto-detection (OpenMapTiles vs Shortbread), and a perf-HUD overlay. OsmBuildingsLayerfor schematic 3D cities —building/building:part/roof
tags with colour / material styling, exponential backoff, and a batched
one-VBO-per-tile mesh.- WFS feature service —
CQL_FILTER, paging, POST fallback, auth hook, GeoJSON / GML
output negotiation, OGC API – Features compatibility, and a viewport-drivenWfsLayer
wrapper. - Shapefile loader —
.shp+.dbf+.prj+.cpg/ language-driver codepages,
ZIP-archive ingest, MultiPatch (shape type 31) →TriangleMesh, UTM reprojection,
surface-drape with depth-offset, lazyrecordsSequence. - NITF 2.1 / NSIF 1.0 imagery — uncompressed grayscale + 3-band RGB.
- DTED elevation tiles (MIL-PRF-89020B) with local-library coverage.
Cache architecture v3 (source-decorator redesign)
- External, source-decorator API.
attachCache(layer, contentKey)wraps the layer's
source in a cache decorator and registers a service-identity row;
listEntries()+openLayer<T>(contentKey)/openElevationCoverage<T>reopen entries
across launches.CacheCategory<T>(typed sealed-class witness) lets
cm.open(CacheCategory.X)bulk-rebuild every entry of one data type with the right
return shape. - Unified entry metadata.
CacheEntry/ContentEntryare the single read/write
surface for an entry'scontentKey,lastModified,contentSize,displayName.
File-format factories (MBTiles, RMaps, ATAK) and self-contained package layers
(Kropyva-style.kme) implementContentEntrydirectly; GPKG / IndexedDB / filesystem
caches route through aContentManagerEntryadapter. Apps dispatch generically against
layer.contentEntry(cm)with no per-format type checks. BulkFeatureLayer+BulkFeatureSource— one streaming pipeline for every
vector-feature source (WFS, Shapefile, and now GeoJSON viaGeoJsonBulkFeatureSource),
with simplestyle keys applied automatically anddensity/
labelVisibilityThreshold/defaultAltitudeMode/autoApplyStyleas layer-level
params. GeoJSON cache opt-in (attachCache+openLayer<BulkFeatureLayer>) replays
rows from the feature store on later launches with no source text required.CachedGeometrygainsMultiPolygon— GeoJSON / GPKGMULTIPOLYGONfeatures no
longer flatten to their first sub-polygon; they fan out to NPolygonrenderables
sharing one properties map.
Other cache infrastructure
- Pure-web IndexedDB caching —
CachedBulkFeatureSource+IndexedDbTileStorefor
image, vector-tile, feature, and elevation data on JS. iOS uses an NSBundle-anchored
FileSystemTileStore+FileSystemFeatureStore. CacheEvictionPolicy(maxEntries/maxAge/maxBytes) across every cache
backend, with sweep-on-bind + explicitevict().- MVT persistent tile cache — GeoPackage on JVM/Android (OGC Image Matters
im_vector_tiles_mapboxextension, readable by QGIS / ogr2ogr / NGA geopackage-java)
and IndexedDB on JS, with HTTP ETag / Last-Modified revalidation. - Bulk-download
overrideCacheflag —TiledImageLayer.launchBulkRetrievaland
TiledElevationCoverage.launchBulkRetrievalcan re-fetch every tile ignoring the cache.
Rendering
- Mapbox style loader — full operator parity,
fill-extrusionlayer type,
text-max-width10em default. ShadowLayer— tighter default cap, lookAt-range scaling, Lambert-modulated
attenuation, soft-fade far cascade edge.- Moments textures gate
GL_LINEARonOES_texture_float_linear(Adreno fix). - JS WebGL2 translates legacy GLSL to ES 3.00 for
dFdx-based lighting. RenderResourceCacheuses page-alignedBufferObjectfootprint for LRU accounting.- Lambertian shading on triangle-shaded shapes.
- Depth-offset clamp-to-ground vector shapes (no more sinking into terrain).
API & package layout
- KML format API made public.
- GeoPackage moved to
earth.worldwind.formats.gpkg(see Breaking changes);ogc/
now contains only network protocols. GeoJsonLayerFactoryreduced to a ~30-line facade overGeoJsonBulkFeatureSourceBulkFeatureLayer.
- New cross-platform GeoJSON and KML tutorials (
commonMain) replace the Android-only
KmlDemoActivity/GeoJsonDemoActivity. Sample documents ship as moko-resources
assets, read per-platform via classpath /AssetManager/NSBundle/fetch.
Dependency bumps
- Compose Multiplatform 1.10.3 → 1.11.0 (drops
iosX64in-compose/
-compose-samples). - Ktor 3.4.3 → 3.5.0.
- Kotlin 2.3.21, kotlinx-coroutines 1.11.0, kotlinx-serialization 1.11.0,
geopackage-android 6.7.5.
Android versionCode 24 → 25.
WorldWind Kotlin v1.14.0
WorldWindKotlin 1.14.0 — Release Notes
Highlights
- iOS support — New Kotlin/Native target (
iosX64,iosArm64,iosSimulatorArm64) across:worldwind,:worldwind-compose,:worldwind-compose-samples, and:worldwind-tutorials. SwiftUI tutorial shell included. - Toolchain bump — Migrated to AGP 9 / Gradle 9 / Kotlin 2.3.21.
- Unified Compose API — Single
@Composable WorldWindowthat works across Android, JVM (Desktop), JS, and iOS. - 3D Ellipsoid shape — New tri-axial ellipsoid renderable with rotation and lighting.
New Features
- Compose:
@Composable WorldWindowunified across Android, JVM, and JS (b29cdc7) - Shapes: 3D rotation / tri-axial Ellipsoid with lighting (caeaa3f)
- Sightline: embedded-receiver path for in-shader self-shadow (f584eaf)
- Navigation: fling + zoom-anchor gestures for 2D projections (2bd48a0)
- GeoTiff: unified reader/writer in
commonMain, dropping the JVM-onlymil.nga.tiffdependency (2815097)
Fixes
- Sightline/Shadow: restore caller's framebuffer instead of unbinding to NONE (12ab237)
- DrawableSightline: directional regression — re-run depth per face (faf7366)
- Path: emit second trailing miter dummy so WebGL accepts surface lines (0a95ae0)
- Shape: invalidate geometry cache on vertical-exaggeration change (938f30e)
- Shapes: keep
BoundingDataacrossreset()to stop fall-through blinking (961a40d) - Placemark/Label: recompute
placePointeach frame (drop stale cartesian cache) (b9c8fd1) - SelectDragDetector: placemark select/drag on macOS Safari (6aa7cb5)
- SelectDragDetector: route depth-tested point into pick/context/double-tap (4117c83)
- PickedObject: opt out of depth unprojection for surface shapes (696f18d)
- WorldWind: 2D pan stuck when subclasses override
gestureDidBegin(7c7426d) - WorldWind: skip
COLLISION_THRESHOLDon globe-state reclamp (8b5c7ea) - RequestRedraw: don't block sequential calls from background threads (050602d)
- Tutorials: request redraw after async COLLADA/glTF parse completes (4ed2c48)
Rendering / Performance
- ViewshedSightline: multi-WorldWindow correctness + RRC-managed output texture (db3f54f)
- Programs: per-WorldWindow shader-program storage (ccff546)
- Programs: zero-overhead no-shadow variants for shadow receivers (2c2f4c8)
- Shadows: per-platform MSM moment bias (66dcf51)
- Atmosphere: drop shadow modulation from
GroundProgramSECONDARY pass (ea3675d) - Shadow: route surface-shape rasterize-pass disable through helper (95f32a2)
- DrawableSightline: dispatch
SightlineReceiverin occlusion passes (7be1f70) - RenderContext: reified
getShaderProgramoverload; dropKEYcompanions (9bcb7c6)
Build / Tooling
- JVM/macOS: tutorials run on Apple Silicon JBR (eaa1ef3)
- Build: strip moko-resources iOS pack on non-Mac hosts (Windows/Linux CI) (79abeec)
- JVM: bump vlcj to 4.12.1 + assorted dependency bumps (3e7e111)
- GeoTiffReader: drop ignored-key/tag log spam to DEBUG (2c87ad9)
- Skip signing for local MavenLocal publishes (56c2033)
- Examples: drop day-night terminator from basic Android activities (e2d9a0e)
Full Changelog: v1.13.1...v1.14.0 (38 commits)
WorldWind Kotlin v1.13.1
Highlights
Directional sun shadows. New ShadowLayer adds cascaded shadow maps with Hamburger 4-moment Moment Shadow Mapping. Shapes (3D meshes, polygons, COLLADA, glTF) cast shadows onto terrain and onto each other from the same sun direction that drives lighting and atmosphere. Opt-in: add ShadowLayer() to the layer list after AtmosphereLayer.
Atmosphere realism. Standard scattering coefficients plus a Black Marble city-lights night side.
WebGL stability. Two pre-existing GL_INVALID_OPERATION feedback-loop sources (one only fired during pick drag) eliminated.
What's New
Shadows
ShadowLayerwith PSSM cascades (default 3) blended uniform/log viasplitBlend.- Hamburger 4-moment MSM and 9-tap rotated PCF receiver paths, selectable via
ShadowLayer.algorithm. - Per-platform defaults: JS → MSM (1-tap analytic Cholesky is fast on WebGL2/ANGLE), JVM/Android → PCF (Adreno can't do MSM cleanly).
ShapeAttributes.shadowMode,ColladaScene.shadowMode,GltfScene.shadowMode— per-shape cast/receive control, decoupled fromisLightingEnabled.- Auto-scaling cascade reach (
maxCascadeDistanceandcasterPullbacktrack camera altitude) so the same defaults work from city view to globe view. - Per-cascade caster + terrain-tile culling, half-resolution far cascade, sun-below-horizon early-exit.
- Stable cascade footprint via bounding-sphere texel snap (no shimmering on camera move).
- Smooth blend across cascade boundaries.
Atmosphere & sky
AtmosphereLayer: standard scattering coefficients (Kr/Km/eSun) plus night-side Black Marble texture sampling.StarFieldLayer: Gaussian star profile, larger sprites, density-aware sun rendering.
Input
- Cursor-perfect drag for clamp-to-ground shapes in
SelectDragDetector. - Fling gesture cancels reliably on every press regardless of which consumer claimed the pointer.
Bug Fixes
ViewshedSightlinepick now returns the sightline itself rather than its internalSurfaceImage.- Pick depth texture left bound to
GL_TEXTURE0after the depth-to-color blit causedGL_INVALID_OPERATIONfeedback errors during back-to-back picks (drag). - Cascade textures left bound from the previous frame's receiver pass formed a feedback loop on the next frame's depth pass under Chromium's sampler-aware WebGL check.
API Additions
// Add directional sun shadows to a scene
engine.layers.addLayer(ShadowLayer())
// Per-shape opt-out
attributes.shadowMode = ShadowMode.RECEIVE_ONLY // building doesn't cast onto itself
attributes.shadowMode = ShadowMode.CAST_ONLY // marker that shouldn't get darkened
attributes.shadowMode = ShadowMode.NONE // skip the pipeline entirely
// Algorithm override (per-platform default applies otherwise)
ShadowLayer().apply { algorithm = ShadowAlgorithm.MSM }Migration Notes
ShadowLayeris opt-in and off by default; existing scenes are unaffected.ShapeAttributes.shadowModedefaults toCAST_AND_RECEIVE. WithoutShadowLayerin the layer list, the field is inert.- Place
ShadowLayerafterAtmosphereLayerso the layer sees the sun direction.
Full Changelog: v1.13.0...v1.13.1
WorldWind Kotlin v1.13.0
New features
- 3D model formats — COLLADA and glTF 2.0 loaders with cross-platform tutorials.
- Projected media surface — drape drone video / photos on terrain via 4-corner homography, plus a 3D camera-frustum projection path driven from KLV pose. (Documented JS/WebGL incompatibility on the 3D path.)
- Sightline / viewshed overhaul
- DEM-based GPU viewshed independent of terrain LoD
- Cast shadows from off-frustum shapes inside sightline range
- Variance Shadow Mapping with separable Gaussian on moments
- Omnidirectional Moment Shadow Mapping with cube-map storage
SightlineOccluderhook so 3D shapes occlude as well
- UI overlay layers — density-aware sizing and cross-platform interaction.
- Improved atmosphere layer and more realistic star field.
- Additional geographic projections.
- Ellipse arc / pie-slice via
startAzimuth+sweepAngle. - Pan fling and cursor / pinch-anchored zoom.
- JVM
KeyboardControls— multi-key hold and per-key acceleration. - Tutorial-selection UI in JVM entry point mirroring JS Main.
DefaultHttpClientexpect/actual with platform engine customization hook (e.g. permissive SSL).- Picking returns Cartesian point reconstructed from pick-pass depth buffer.
Location.fromString— strict, format-autodetecting parser.
Rendering / globe robustness
- Antimeridian + pole rendering for surface Polygon / Path / Ellipse, plus surface shapes that cross the antimeridian.
- 3D Polygon Cartesian tessellation for pole-encircling polygons; top-plane lat/lon GLU input with antimeridian unwrap.
- MSAA antialiasing for surface shapes.
- Skip depth-write on translucent fragments.
- 2D globe: hide tilt and exaggeration controls.
- Camera clamps to terrain on globe-state change.
GoToAnimatoranimates tilt to 0 so the finallookAtmatches the target.
Interaction & input
- Drag picks point in parallel with cursor (no snap of ref to cursor).
- View controls: live cursor tracking and orphan-repeat fix.
- JS controller: touch events for minimap and view controls; finger-drag fix and removed dead zone; click events from drags are now swallowed.
- JVM HiDPI mouse-coordinate offset fix.
Performance & internals
- Smarter per-frame shape-assembly budget with graceful fall-through.
- Pre-allocated arrays in index-buffer assembly (replacing
List<Short>/List<Int>). Long → Intbuffer / data version counters.GpkgContentManagerdispatches SQLite work toDispatchers.IO.- Cached Cartesian position in Placemark and Label (with correctness fix).
- Reduced maximum star size.
- Carry-overs from 1.11.x:
LongLruMemoryCache(noLongboxing), elevation-decoder optimizations, async JVM frame rendering, throttled shape geometry assembly, drawable-queue sort optimizations, GLU tessellator cleanup, removal of per-frameNumericArraywrappers, eye-distance threshold cached on instances.
Bug fixes
DrawableQueuesort-key encoding for negative order values.- Deprecated
onLowMemory/TRIM_MEMORYcallbacks replaced. ExtractKlvanchors JSON timeline at the video's first PTS.- Raw path variable shadowing addressed.
- Several MilStd2525 generation fixes; mil-sym-2525 deps bumped.
- Documentation and test warnings cleaned up.
Tutorials
- Depth-pick visualization, Android
Movabledrag, JVM SSL hook. - Refreshed Android HTML help to match current Kotlin sources.
Full changelog: v1.11.0...v1.13.0