diff --git a/examples/files.json b/examples/files.json
index 62cd99178c3e83..e9672f03d8eefd 100644
--- a/examples/files.json
+++ b/examples/files.json
@@ -356,6 +356,7 @@
"webxr_ar_hittest",
"webxr_ar_lighting",
"webxr_ar_paint",
+ "webxr_ar_plane_detection",
"webxr_vr_ballshooter",
"webxr_vr_cubes",
"webxr_vr_dragging",
diff --git a/examples/screenshots/webxr_ar_plane_detection.jpg b/examples/screenshots/webxr_ar_plane_detection.jpg
new file mode 100644
index 00000000000000..001fe203e0b94e
Binary files /dev/null and b/examples/screenshots/webxr_ar_plane_detection.jpg differ
diff --git a/examples/webxr_ar_plane_detection.html b/examples/webxr_ar_plane_detection.html
new file mode 100644
index 00000000000000..053284e6f1efce
--- /dev/null
+++ b/examples/webxr_ar_plane_detection.html
@@ -0,0 +1,167 @@
+
+
+
+ three.js ar - plane detection
+
+
+
+
+
+
+
+
three.js ar - plane detection
(Chrome Android 81+)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderers/webxr/WebXRManager.js b/src/renderers/webxr/WebXRManager.js
index ebbdff44eaa24a..89b1df0e564f96 100644
--- a/src/renderers/webxr/WebXRManager.js
+++ b/src/renderers/webxr/WebXRManager.js
@@ -43,6 +43,9 @@ class WebXRManager extends EventDispatcher {
const controllers = [];
const controllerInputSources = [];
+ const planes = new Set();
+ const planesLastChangedTimes = new Map();
+
//
const cameraL = new PerspectiveCamera();
@@ -600,6 +603,12 @@ class WebXRManager extends EventDispatcher {
};
+ this.getPlanes = function () {
+
+ return planes;
+
+ };
+
// Animation Loop
let onAnimationFrameCallback = null;
@@ -708,6 +717,65 @@ class WebXRManager extends EventDispatcher {
if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
+ if ( frame.detectedPlanes ) {
+
+ scope.dispatchEvent( { type: 'planesdetected', data: frame.detectedPlanes } );
+
+ let planesToRemove = null;
+
+ for ( const plane of planes ) {
+
+ if ( ! frame.detectedPlanes.has( plane ) ) {
+
+ if ( planesToRemove === null ) {
+
+ planesToRemove = [];
+
+ }
+
+ planesToRemove.push( plane );
+
+ }
+
+ }
+
+ if ( planesToRemove !== null ) {
+
+ for ( const plane of planesToRemove ) {
+
+ planes.delete( plane );
+ planesLastChangedTimes.delete( plane );
+ scope.dispatchEvent( { type: 'planeremoved', data: plane } );
+
+ }
+
+ }
+
+ for ( const plane of frame.detectedPlanes ) {
+
+ if ( ! planes.has( plane ) ) {
+
+ planes.add( plane );
+ planesLastChangedTimes.set( plane, frame.lastChangedTime );
+ scope.dispatchEvent( { type: 'planeadded', data: plane } );
+
+ } else {
+
+ const lastKnownTime = planesLastChangedTimes.get( plane );
+
+ if ( plane.lastChangedTime > lastKnownTime ) {
+
+ planesLastChangedTimes.set( plane, plane.lastChangedTime );
+ scope.dispatchEvent( { type: 'planechanged', data: plane } );
+
+ }
+
+ }
+
+ }
+
+ }
+
xrFrame = null;
}