ImageOverlayPlugin: errors and black tiles when an overlay is swapped while tiles load · Issue #1769 · NASA-AMMOS/3DTilesRendererJS · GitHub
Skip to content

ImageOverlayPlugin: errors and black tiles when an overlay is swapped while tiles load #1769

Open

Description

@keiken-shin

Description

When I swap an overlay while tiles load, ImageOverlayPlugin throws three errors.
I swap it like this: await next.init(), then deleteOverlay( old ), then addOverlay( next ).

  • Cannot read properties of undefined (reading 'signal'): unhandled promise rejection.
  • Cannot read properties of null (reading 'updateMatrixWorld'): unhandled promise rejection.
  • Cannot read properties of undefined (reading 'color'): thrown by renderer.render(), on every frame.

What you see: some tiles never get the new overlay, and the render stops at those tiles.
So part of the map stays black until those tiles load again.

I think there are three small causes, all in src/three/plugins/images/ImageOverlayPlugin.js:

  1. reading 'signal': a tile joins processedTiles on tile-download-start, but it gets its tileControllers entry only later, in _processTileModel.
    _initOverlay goes over all processedTiles, so _initTileSceneOverlayInfo reads tileController.signal of a tile that has no controller yet.
    The comment above that line says a missing controller means the tile was disposed, but the code does not check for it.

  2. reading 'updateMatrixWorld': while a tile is in _processTileModel, its scene is only in pendingTiles, and tile.engineData.scene is still null.
    _initOverlay reads only tile.engineData.scene, so _initTileSceneOverlayInfo gets null.
    That tile then never gets the new overlay.

  3. reading 'color': after (2), the new overlay has no meshInfo for that tile.
    _updateLayers still sets LAYER_COUNT to overlays.length (from the meshes of the other overlays), but layerInfo.value[ i ] stays empty for the new overlay.
    The uniform upload then reads .color of undefined.
    Side note: params.layerMaps.length = overlays.length sets a property on the uniform object, not on its value array.

I have a fix for all three.
With it, the same steps give no errors, and every tile shows the overlay:

  1. _initTileSceneOverlayInfo: also return early when tileController is missing.
  2. _initOverlay: use tile.engineData.scene || pendingTiles.get( tile ), and skip a tile that has no scene yet (_processTileModel adds every overlay to it later).
  3. _updateLayers: before the layers are filled, give every mesh of the tile one slot per overlay (layerMaps.value[ i ] = null, layerInfo.value[ i ] = overlay), and set the length on the value arrays.

I can open a pull request with these changes if this approach looks right to you.

Reproduction steps

  1. Open the live example. It uses GeneratedSurfacePlugin with OpenStreetMap imagery (XYZTilesOverlay), and a red grid as a GeoJSONOverlay.
  2. The page shows the area with the grid, and waits until all tiles are loaded. For the first few seconds the view is all red: on the first, coarse tiles the grid lines blur together (this also happens with the fix).
  3. The camera moves closer. As soon as the new tiles are being processed, the page swaps the grid overlay once.
  4. At that swap, the console shows the signal and updateMatrixWorld errors.
  5. Right after it, some tiles show no grid, that part of the map turns black, and reading 'color' is logged on every frame.

Code

// a generated globe with OpenStreetMap imagery
const imagery = new XYZTilesOverlay( { url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', levels: 19 } );
const plugin = new ImageOverlayPlugin( { overlays: [ imagery ], renderer } );
const tiles = new TilesRenderer();
tiles.registerPlugin( new GeneratedSurfacePlugin( { overlay: imagery } ) );
tiles.registerPlugin( plugin );

// swap a GeoJSON overlay while tiles load: wait until the new one is ready, remove the old one, add the new one
let current = null;
async function swap() {

	const next = new GeoJSONOverlay( { geojson, strokeStyle: '#ff2d2d', strokeWidth: 2 } );
	await next.init();
	if ( current ) plugin.deleteOverlay( current );
	plugin.addOverlay( next );
	current = next;

}

Live example

Codepen Link

Screenshots & Tile Set

Error stacks (v0.5.3 from jsDelivr)
TypeError: Cannot read properties of undefined (reading 'signal')
    at pn._initTileSceneOverlayInfo (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1889:63)
    at 3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1846:17
    at Set.forEach (<anonymous>)
    at pn._initOverlay (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1843:5)
    at pn.addOverlay (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1819:26)
    at swap (index.html:52:9)

TypeError: Cannot read properties of null (reading 'updateMatrixWorld')
    at pn._initTileSceneOverlayInfo (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1891:5)
    at 3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1846:17
    at Set.forEach (<anonymous>)
    at pn._initOverlay (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1843:5)
    at pn.addOverlay (3d-tiles-renderer@0.5.3/build/plugins-DXnt62ch.js:1819:26)
    at swap (index.html:52:9)

TypeError: Cannot read properties of undefined (reading 'color')
    at StructuredUniform.setValue (three@0.186.1/build/three.module.js:5979:25)
    at StructuredUniform.setValue (three@0.186.1/build/three.module.js:5979:6)
    at WebGLUniforms.upload (three@0.186.1/build/three.module.js:6133:7)
    at setProgram (three@0.186.1/build/three.module.js:18828:19)
    at WebGLRenderer.renderBufferDirect (three@0.186.1/build/three.module.js:17239:20)
    at renderObject (three@0.186.1/build/three.module.js:18216:11)

Screenshots

Before:
Image
After (the fix applied):
Image

Library Version

v0.5.3 (the same code is on master, 075b123)

Three.js Version

r186

Activity

  1. mikemikimike commented on Oct 14, 2026

    @mikemikimike
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions