Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions example/three/googleMapsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
GLTFExtensionsPlugin,
BatchedTilesPlugin,
CesiumIonAuthPlugin,
ImageOverlayPlugin,
PMTilesOverlay,
} from '3d-tiles-renderer/plugins';
import { MVTAnnotationsPlugin } from '../../src/three/plugins/mvt/MVTAnnotationsPlugin.js';
import {
Scene,
WebGLRenderer,
Expand Down Expand Up @@ -89,6 +92,17 @@ function reinstantiateTiles() {

}

const overlay = new PMTilesOverlay( {
url: 'https://data.source.coop/protomaps/openstreetmap/v4.pmtiles',
} );

tiles.registerPlugin( new ImageOverlayPlugin( { overlays: [ overlay ], renderer } ) );
tiles.registerPlugin( new MVTAnnotationsPlugin( {
overlay,
camera: transition.camera,
scene,
} ) );

tiles.group.rotation.x = - Math.PI / 2;
scene.add( tiles.group );

Expand Down Expand Up @@ -123,6 +137,7 @@ function init() {
tiles.deleteCamera( prevCamera );
tiles.setCamera( camera );
controls.setCamera( camera );
tiles.getPluginByName( 'MVT_ANNOTATIONS_PLUGIN' )?.setCamera( camera );

} );

Expand Down
7 changes: 7 additions & 0 deletions example/three/pmtiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PMTilesOverlay,
GeneratedSurfacePlugin,
} from '3d-tiles-renderer/plugins';
import { MVTAnnotationsPlugin } from '../../src/three/plugins/mvt/MVTAnnotationsPlugin.js';
import GUI from 'three/addons/libs/lil-gui.module.min.js';

// Protomaps "Light" theme — from protomaps/basemaps flavors.ts
Expand Down Expand Up @@ -139,6 +140,12 @@ function init() {
overlays: [ overlay ],
renderer,
} ) );
const annotationsPlugin = new MVTAnnotationsPlugin( {
overlay,
camera,
} );
// annotationsPlugin.getAnnotation = ( layerName ) => layerName === 'places';
tiles.registerPlugin( annotationsPlugin );

tiles.setCamera( camera );
tiles.group.rotation.x = - Math.PI / 2;
Expand Down
18 changes: 14 additions & 4 deletions src/core/renderer/tiles/TilesRendererBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -1064,21 +1064,31 @@ export class TilesRendererBase {

disposeTile( tile ) {

// TODO: are these necessary? Are we disposing tiles when they are currently visible?
// Need to mirror the "traverseFunctions" behavior for empty tiles (eg internal tile sets)
if ( tile.traversal.visible ) {

this.invokeOnePlugin( plugin => plugin.setTileVisible && plugin.setTileVisible( tile, false ) );
if ( tile.internal.hasRenderableContent ) {

this.invokeOnePlugin( plugin => plugin.setTileVisible && plugin.setTileVisible( tile, false ) );

} else {

this.invokeOnePlugin( plugin => plugin.setEmptyTileVisible && plugin.setEmptyTileVisible( tile, false ) );

}

tile.traversal.visible = false;

}

if ( tile.traversal.active ) {
if ( tile.traversal.active && tile.internal.hasRenderableContent ) {

this.invokeOnePlugin( plugin => plugin.setTileActive && plugin.setTileActive( tile, false ) );
tile.traversal.active = false;

}

tile.traversal.active = false;

const { scene } = tile.engineData;
if ( scene ) {

Expand Down
228 changes: 228 additions & 0 deletions src/three/plugins/mvt/DelayedScreenOccupationManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { EventDispatcher } from 'three';
import { ScreenOccupationManager } from './ScreenOccupationManager.js';

export class DelayedScreenOccupationManager extends EventDispatcher {

get matrix() {

return this._inner.matrix;

}

set matrix( v ) {

this._inner.matrix = v;

}

get cameraPosition() {

return this._inner.cameraPosition;

}

set cameraPosition( v ) {

this._inner.cameraPosition = v;

}

get resolution() {

return this._inner.resolution;

}

get size() {

return this._inner.size;

}

set size( v ) {

this._inner.size = v;

}

get cells() {

return this._inner.cells;

}

get sortCallback() {

return this._inner.sortCallback;

}

set sortCallback( v ) {

this._inner.sortCallback = v;

}

constructor() {

super();

this._inner = new ScreenOccupationManager();

this.visible = new Set();
this.showDelay = 0.1;
this.hideDelay = 0.5;

// item -> timer
this._showTimers = new Map();
this._hideTimers = new Map();
this._lastUpdateTime = - 1;

this.added = new Set();
this.removed = new Set();

this._inner.addEventListener( 'added', ( { items } ) => {

const { _showTimers, _hideTimers, visible } = this;
for ( const item of items ) {

_hideTimers.delete( item );
if ( ! visible.has( item ) ) {

_showTimers.set( item, 0 );

}

}

} );

this._inner.addEventListener( 'removed', ( { items } ) => {

const { _showTimers, _hideTimers, visible } = this;
for ( const item of items ) {

_showTimers.delete( item );
if ( visible.has( item ) ) {

_hideTimers.set( item, 0 );

}

}

} );

}

register( item ) {

const { _showTimers, _hideTimers, visible } = this;
const existing = this._inner.getById( item.id );
if ( existing !== undefined ) {

// LoD swap: transfer timer and visibility state from old instance to new
if ( _showTimers.has( existing ) ) {

_showTimers.set( item, _showTimers.get( existing ) );
_showTimers.delete( existing );

}

if ( _hideTimers.has( existing ) ) {

_hideTimers.set( item, _hideTimers.get( existing ) );
_hideTimers.delete( existing );

}

if ( visible.has( existing ) ) {

visible.delete( existing );
visible.add( item );

}

}

this._inner.register( item );

}

unregister( item ) {

this._inner.unregister( item );

}

update() {

const now = performance.now() / 1000;
const dt = this._lastUpdateTime < 0 ? 0 : Math.min( now - this._lastUpdateTime, 0.1 );
this._lastUpdateTime = now;

// fires 'added'/'removed' synchronously, populating the timers
this._inner.update();

const {
_showTimers,
_hideTimers,
visible,
added,
removed,
showDelay,
hideDelay,
} = this;

added.clear();
removed.clear();

for ( const [ item, elapsed ] of _showTimers ) {

const next = elapsed + dt;
if ( next >= showDelay ) {

_showTimers.delete( item );
visible.add( item );
added.add( item );

} else {

_showTimers.set( item, next );

}

}

for ( const [ item, elapsed ] of _hideTimers ) {

const next = elapsed + dt;
if ( next >= hideDelay ) {

_hideTimers.delete( item );
visible.delete( item );
removed.add( item );

} else {

_hideTimers.set( item, next );

}

}

if ( added.size > 0 ) {

this.dispatchEvent( { type: 'added', items: added } );

}

if ( removed.size > 0 ) {

this.dispatchEvent( { type: 'removed', items: removed } );

}

}

}
73 changes: 73 additions & 0 deletions src/three/plugins/mvt/FontAtlasTexture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { GlyphAtlasTexture } from './GlyphAtlasTexture.js';

export class FontAtlasTexture extends GlyphAtlasTexture {

constructor( slotCount, slotSize, font, color = 'white' ) {

super( slotCount, slotSize );

this.font = font;
this.color = color;
this._refCounts = new Map();
this._evictionQueue = new Set();

}

// Increments the reference count for char, drawing it into the atlas if not already present.
// Returns the slot { x, y, w, h }.
add( char ) {

const { _refCounts, _evictionQueue, font, color } = this;
const count = ( _refCounts.get( char ) ?? 0 ) + 1;
_refCounts.set( char, count );

if ( this.has( char ) ) {

// already in atlas — pull it out of the eviction queue
_evictionQueue.delete( char );

} else {

if ( this.isFull ) {

const candidate = _evictionQueue.values().next().value;
if ( candidate === undefined ) {

throw new Error( 'FontAtlasTexture: atlas is full.' );

}

_evictionQueue.delete( candidate );
_refCounts.delete( candidate );
super.release( candidate );

}

this.drawGlyph( char, char, font, color );

}

return this.get( char );

}

// Decrements the reference count for char. The slot is kept in the atlas
// and only evicted when space is needed for a new glyph.
release( char ) {

const { _refCounts, _evictionQueue } = this;
const count = _refCounts.get( char ) ?? 0;
if ( count <= 1 ) {

_refCounts.set( char, 0 );
_evictionQueue.add( char );

} else {

_refCounts.set( char, count - 1 );

}

}

}
Loading
Loading