@@ -5,8 +5,8 @@ import {DetectedMesh} from './DetectedMesh';
55import { MeshDetectionOptions } from './MeshDetectionOptions' ;
66import { Physics } from '../../physics/Physics' ;
77
8- const SEMANTIC_LABELS = [ 'Floor ' , 'Ceiling ' , 'Wall' , 'Table '] ;
9- const SEMANTIC_COLORS = [ 0x00ff00 , 0xff0000 , 0x0000ff , 0xffff00 ] ;
8+ const SEMANTIC_LABELS = [ 'floor ' , 'ceiling ' , 'wall ' ] ;
9+ const SEMANTIC_COLORS = [ 0x00ff00 , 0xffff00 , 0x0000ff ] ;
1010
1111// Wrapper around WebXR Mesh Detection API
1212// https://immersive-web.github.io/real-world-meshing/
@@ -23,6 +23,23 @@ export class MeshDetector extends Script {
2323 private physics ?: Physics ;
2424 private defaultMaterial = new THREE . MeshBasicMaterial ( { visible : false } ) ;
2525
26+ // Optimization1: Camera culling constants
27+ private readonly kMaxViewDistance = 3.0 ;
28+ private readonly kFOVCosThreshold = 0.25 ;
29+
30+ // Optimization2: Limit the number of meshes processed per frame (Not used)
31+ private readonly MAX_MESHES_PER_FRAME = 100000 ; // Process only n meshes per frame
32+
33+ // Optimization3: Mesh update throttling (similar to ARCore reflection cube map in /usr/local/google/home/adamren/Desktop/xrlabs/arlabs/xrblocks/samples/lighting)
34+ private lastMeshUpdateTime = 0 ;
35+ private readonly MESH_UPDATE_INTERVAL_MS = 1000 ;
36+
37+ // Optimization4: Cleanup old meshes (ToDo: Not used)
38+ private meshLastSeenTime = new Map < XRMesh , number > ( ) ;
39+ private readonly MAX_MESH_COUNT = 200 ; // Limit total mesh count
40+ private readonly MESH_CLEANUP_INTERVAL_MS = 5000 ; // Cleanup every 5 seconds
41+ private lastCleanupTime = 0 ;
42+
2643 override init ( {
2744 options,
2845 renderer,
@@ -62,19 +79,66 @@ export class MeshDetector extends Script {
6279 const meshes = frame ?. detectedMeshes ;
6380 if ( ! meshes ) return ;
6481
82+ // Throttle mesh updates to ~30fps while rendering continues at full rate
83+ const now = performance . now ( ) ;
84+ const timeSinceLastUpdate = now - this . lastMeshUpdateTime ;
85+
86+ if ( timeSinceLastUpdate < this . MESH_UPDATE_INTERVAL_MS ) {
87+ // Skip mesh update this frame - rendering continues without blocking
88+ return ;
89+ }
90+
91+ this . lastMeshUpdateTime = now ;
92+
93+ const referenceSpace = this . renderer . xr . getReferenceSpace ( ) ;
94+ if ( ! referenceSpace ) return ;
95+ const { position : cameraPosition , forward : cameraForward } =
96+ this . getCameraInfo ( frame , referenceSpace ) ;
97+
6598 // Delete old meshes
6699 for ( const [ xrMesh , threeMesh ] of this . xrMeshToThreeMesh . entries ( ) ) {
67100 if ( ! meshes . has ( xrMesh ) ) {
68101 this . xrMeshToThreeMesh . delete ( xrMesh ) ;
69102 this . threeMeshToXrMesh . delete ( threeMesh ) ;
70- threeMesh . geometry . dispose ( ) ;
103+ threeMesh . dispose ( ) ;
71104 this . remove ( threeMesh ) ;
72105 }
73106 }
74107
75- // Add new meshes
108+ // Limit processing to avoid frame drops
109+ let processedCount = 0 ;
110+ // const limitedMeshes = Array.from(meshes).slice(0, this.MAX_MESHES_PER_FRAME);
111+ // const testMeshes = new Set(limitedMeshes);
112+ // Process meshes with camera culling BEFORE creating/updating them
76113 for ( const xrMesh of meshes ) {
114+ if ( processedCount >= this . MAX_MESHES_PER_FRAME ) break ;
115+
116+ // Camera culling: only process visible meshes
117+ if (
118+ ! this . shouldShowMeshInView (
119+ xrMesh ,
120+ cameraPosition ,
121+ cameraForward ,
122+ frame ,
123+ referenceSpace
124+ )
125+ ) {
126+ // If mesh exists but is not visible, remove it for performance
127+ if ( this . xrMeshToThreeMesh . has ( xrMesh ) ) {
128+ const threeMesh = this . xrMeshToThreeMesh . get ( xrMesh ) ! ;
129+ this . xrMeshToThreeMesh . delete ( xrMesh ) ;
130+ this . threeMeshToXrMesh . delete ( threeMesh ) ;
131+ threeMesh . dispose ( ) ;
132+ this . remove ( threeMesh ) ;
133+ }
134+ continue ; // Skip this mesh - don't create or update it
135+ }
136+
137+ processedCount ++ ;
138+
139+ // Only process meshes that pass camera culling
77140 if ( ! this . xrMeshToThreeMesh . has ( xrMesh ) ) {
141+ // New mesh - create it
78142 const threeMesh = this . createMesh ( frame , xrMesh ) ;
79143 this . xrMeshToThreeMesh . set ( xrMesh , threeMesh ) ;
80144 this . threeMeshToXrMesh . set ( threeMesh , xrMesh ) ;
@@ -86,6 +150,7 @@ export class MeshDetector extends Script {
86150 ) ;
87151 }
88152 } else {
153+ // Existing mesh - update vertices and pose
89154 const threeMesh = this . xrMeshToThreeMesh . get ( xrMesh ) ! ;
90155 threeMesh . updateVertices ( xrMesh ) ;
91156 this . updateMeshPose ( frame , xrMesh , threeMesh ) ;
@@ -114,4 +179,83 @@ export class MeshDetector extends Script {
114179 mesh . quaternion . copy ( pose . transform . orientation ) ;
115180 }
116181 }
182+
183+ /**
184+ * Gets camera position and forward vector from XR frame.
185+ */
186+ private getCameraInfo (
187+ frame : XRFrame ,
188+ referenceSpace : XRReferenceSpace
189+ ) : {
190+ position : THREE . Vector3 ;
191+ forward : THREE . Vector3 ;
192+ } {
193+ const viewerPose = frame . getViewerPose ( referenceSpace ) ;
194+ const cameraPosition = new THREE . Vector3 ( 0 , 0 , 0 ) ;
195+ let cameraForward = new THREE . Vector3 ( 0 , 0 , - 1 ) ;
196+
197+ if ( viewerPose && viewerPose . views && viewerPose . views . length > 0 ) {
198+ // Get camera position from first view's transform
199+ const viewTransform = viewerPose . views [ 0 ] . transform ;
200+ const viewMatrix = new THREE . Matrix4 ( ) . fromArray ( viewTransform . matrix ) ;
201+ cameraPosition . setFromMatrixPosition ( viewMatrix ) ;
202+
203+ // Extract forward vector from matrix (typically -Z axis)
204+ const forward = new THREE . Vector3 ( 0 , 0 , - 1 ) ;
205+ forward . applyMatrix4 ( viewMatrix ) ;
206+ forward . sub ( cameraPosition ) . normalize ( ) ;
207+ cameraForward = forward ;
208+ }
209+
210+ return { position : cameraPosition , forward : cameraForward } ;
211+ }
212+
213+ /**
214+ * Checks if mesh should be visible based on camera position and FOV.
215+ */
216+ private shouldShowMeshInView (
217+ mesh : XRMesh ,
218+ cameraPosition : THREE . Vector3 ,
219+ cameraForward : THREE . Vector3 ,
220+ frame : XRFrame ,
221+ referenceSpace : XRReferenceSpace
222+ ) : boolean {
223+ const meshPose = frame . getPose ( mesh . meshSpace , referenceSpace ) ;
224+ if ( ! meshPose ) {
225+ return true ; // Default to showing if no pose available
226+ }
227+
228+ const meshMatrix = new THREE . Matrix4 ( ) . fromArray ( meshPose . transform . matrix ) ;
229+ const meshPosition = new THREE . Vector3 ( ) ;
230+ meshPosition . setFromMatrixPosition ( meshMatrix ) ;
231+
232+ // Calculate distance
233+ const dx = meshPosition . x - cameraPosition . x ;
234+ const dy = meshPosition . y - cameraPosition . y ;
235+ const dz = meshPosition . z - cameraPosition . z ;
236+ const distance = Math . sqrt ( dx * dx + dy * dy + dz * dz ) ;
237+
238+ if ( distance > this . kMaxViewDistance ) {
239+ return false ;
240+ }
241+
242+ // Calculate direction vector and dot product (FOV check)
243+ if ( distance > 0.001 ) {
244+ const invDistance = 1.0 / distance ;
245+ const dirX = dx * invDistance ;
246+ const dirY = dy * invDistance ;
247+ const dirZ = dz * invDistance ;
248+
249+ const dotForward =
250+ dirX * cameraForward . x +
251+ dirY * cameraForward . y +
252+ dirZ * cameraForward . z ;
253+
254+ if ( dotForward < this . kFOVCosThreshold ) {
255+ return false ;
256+ }
257+ }
258+
259+ return true ;
260+ }
117261}
0 commit comments