Skip to content

Commit f524ea9

Browse files
authored
MVT Annotations: Improve raycast performance (#1635)
* Add bvh for perf improvement * Improve processing step * Convert to generator, use ObjectBVH for acceleration * Improve object bvh perf, precalculate resolution * Add comments * Small improvements * Cleanup * Fix objectbvh genertion * Small fixes * Remove empty line
1 parent 5c5b555 commit f524ea9

6 files changed

Lines changed: 236 additions & 101 deletions

File tree

example/three/annotationsExample.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
2626
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
2727
import { AnnotationPoints } from './src/plugins/mvt/AnnotationPoints.js';
28+
import { MeshBVHPlugin } from './src/plugins/MeshBVHPlugin.js';
2829

2930
// CDN source for the icons
3031
const MAKI_BASE = 'https://cdn.jsdelivr.net/npm/@mapbox/maki@8/icons/';
@@ -132,13 +133,13 @@ function reinstantiateTiles() {
132133
} );
133134

134135
tiles = new TilesRenderer();
135-
tiles.accelerateRaycast = false;
136136
tiles.registerPlugin( new UpdateOnChangePlugin() );
137137
tiles.registerPlugin( new CesiumIonAuthPlugin( { apiToken: import.meta.env.VITE_ION_KEY, assetId: '2275207', autoRefreshToken: true } ) );
138138
tiles.registerPlugin( new GLTFExtensionsPlugin( {
139139
dracoLoader: new DRACOLoader().setDecoderPath( 'https://unpkg.com/three@0.153.0/examples/jsm/libs/draco/gltf/' )
140140
} ) );
141141
tiles.registerPlugin( new TilesFadePlugin() );
142+
tiles.registerPlugin( new MeshBVHPlugin() );
142143
tiles.registerPlugin( new MVTAnnotationsPlugin( {
143144
overlay,
144145
camera,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { MeshBVH, ObjectBVH, acceleratedRaycast } from 'three-mesh-bvh';
2+
3+
/**
4+
* Demo plugin that synchronously builds a three-mesh-bvh BVH for every mesh
5+
* in each loaded tile & a full-tileset BVH. Attach the plugin before loading
6+
* tiles so BVHs are available immediately in processTileModel / load-model callbacks.
7+
*/
8+
export class MeshBVHPlugin {
9+
10+
constructor() {
11+
12+
this.name = 'MESH_BVH_PLUGIN';
13+
this.tiles = null;
14+
this.objectBvh = null;
15+
this.needsUpdate = true;
16+
17+
}
18+
19+
init( tiles ) {
20+
21+
this.tiles = tiles;
22+
23+
// events
24+
this._onLoadModel = ( { scene } ) => {
25+
26+
scene.traverse( c => {
27+
28+
if ( c.isMesh ) {
29+
30+
c.geometry.boundsTree = new MeshBVH( c.geometry );
31+
c.raycast = acceleratedRaycast;
32+
33+
}
34+
35+
} );
36+
37+
};
38+
39+
this._onDisposeModel = ( { scene } ) => {
40+
41+
scene.traverse( c => {
42+
43+
if ( c.isMesh ) {
44+
45+
c.geometry.boundsTree = null;
46+
47+
}
48+
49+
} );
50+
51+
};
52+
53+
this._onVisibilityChange = () => {
54+
55+
// TODO: a dynamic BVH would be best here to save time & memory thrash
56+
this.needsUpdate = true;
57+
58+
};
59+
60+
// registration
61+
tiles.addEventListener( 'load-model', this._onLoadModel );
62+
tiles.addEventListener( 'dispose-model', this._onDisposeModel );
63+
tiles.addEventListener( 'tile-visibility-change', this._onVisibilityChange );
64+
65+
// replace the raycast function, re-computing the object bvh if needed
66+
tiles.group.raycast = ( ...args ) => {
67+
68+
if ( this.needsUpdate || ! this.objectBvh ) {
69+
70+
this.objectBvh = new ObjectBVH( tiles.group );
71+
this.needsUpdate = false;
72+
73+
}
74+
75+
this.objectBvh.raycast( ...args );
76+
return false;
77+
78+
};
79+
80+
// initialize existing scenes
81+
tiles.forEachLoadedModel( ( scene ) => {
82+
83+
this._onLoadModel( { scene } );
84+
85+
} );
86+
87+
}
88+
89+
dispose() {
90+
91+
const { tiles } = this;
92+
93+
// revert to the prototype chain raycast
94+
delete tiles.group.raycast;
95+
96+
// remove events
97+
tiles.removeEventListener( 'load-model', this._onLoadModel );
98+
tiles.removeEventListener( 'dispose-model', this._onDisposeModel );
99+
tiles.removeEventListener( 'tile-visibility-change', this._onVisibilityChange );
100+
101+
tiles.forEachLoadedModel( ( scene ) => {
102+
103+
this._onDisposeModel( { scene } );
104+
105+
} );
106+
107+
}
108+
109+
}

package-lock.json

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@
8181
"author": "Garrett Johnson <garrett.kjohnson@gmail.com>",
8282
"license": "Apache-2.0",
8383
"dependencies": {
84+
"@mapbox/vector-tile": "^2.0.3",
8485
"pbf": "^4.0.1",
85-
"pmtiles": "^4.3.2",
86-
"@mapbox/vector-tile": "^2.0.3"
86+
"pmtiles": "^4.3.2"
8787
},
8888
"devDependencies": {
8989
"@babel/preset-modules": "^0.1.6",
@@ -113,6 +113,7 @@
113113
"lil-gui": "^0.21.0",
114114
"postprocessing": "^6.36.4",
115115
"three": "^0.170.0",
116+
"three-mesh-bvh": "^0.9.10",
116117
"typescript": "^5.6.0",
117118
"typescript-eslint": "^8.48.1",
118119
"vite": "^8.0.16",

0 commit comments

Comments
 (0)