Skip to content

Commit

Permalink
Add support for WebXR Mesh and WebXR Plane modules
Browse files Browse the repository at this point in the history
  • Loading branch information
dmarcos committed Nov 14, 2023
1 parent 4418c48 commit 3764878
Show file tree
Hide file tree
Showing 10 changed files with 477 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
"ecmaVersion": 6
}
},
{
/* This module uses ES6 */
"files": ["./src/components/scene/real-world-meshing.js"],
"parserOptions": {
"ecmaVersion": 6
}
},
{
/* This module uses ES8 async / await due to WebXR Anchor Module integration */
"files": ["./src/components/anchored.js"],
Expand Down
29 changes: 29 additions & 0 deletions docs/components/real-world-meshing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: real-world-meshing
type: components
layout: docs
parent_section: components
source_code: src/components/scene/real-world-meshing.js
examples: []
---

Set this component on the scene element to render meshes corresponding to 3D surfaces detected in user's enviornment: this includes planes and meshes corresponding to floor, ceiling, walls and other objects. Each plane or meshes comes with a label indicating the type of surface or object.

This component requires a browser with support for the [WebXR Mesh Detection Module]([object
pooling](https://en.wikipedia.org/wiki/Object_pool_pattern) and the [WebXR Plane Detection Module](https://immersive-web.github.io/real-world-geometry/plane-detection.html). The system / headset used might require additional scene setup by the use like setting up floor, walls, ceiling or labeling furniture in the space.

## Example

```html
<a-scene real-world-meshing></a-scene>
```

## Properties

| Property | Description | Default Value
|---------------|---------------------------------------------------------------------------------------|---------------
| filterLabels | List of labels corresponding to the surfaces that will be rendered. Can constrain rendering to certain surfaces like desks, walls, tables... All surfaces will be rendered if left empty. | [] |
| meshesEnabled | If meshes will be rendered as returned by the WebXR Mesh Detection Module. | true |
| meshMixin | Mixin applied to the entities corresponding to the detected meshes. | '' |
| planesEnabled | If planes will be rendered as returned by the WebXR Plane Detection Module. | true |
| planeMixin | Mixin applied to the entities corresponding to the detected planes. | '' |
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ <h2>Examples</h2>
<li><a href="boilerplate/360-video/">360&deg; Video</a></li>
<li><a href="boilerplate/3d-model/">3D Model (glTF)</a></li>
<li><a href="mixed-reality/anchor/">Anchor (Mixed Reality)</a></li>
<li><a href="mixed-reality/real-world-meshing/">Real World Meshing (Mixed Reality)</a></li>
</ul>

<h2>Examples from Documentation</h2>
Expand Down
30 changes: 30 additions & 0 deletions examples/js/center-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* global AFRAME, THREE */
AFRAME.registerComponent('center-model', {
init: function () {
var model;
this.onModelLoaded = this.onModelLoaded.bind(this);
model = this.el.components['gltf-model'] && this.el.components['gltf-model'].model;
if (model) {
this.centerModel(model);
return;
}
document.addEventListener('model-loaded', this.onModelLoaded);
},

onModelLoaded: function () {
var model = this.el.components['gltf-model'].model;
this.centerModel(model);
},

centerModel: function (model) {
var box;
var center;
this.el.removeObject3D('mesh');
box = new THREE.Box3().setFromObject(model);
center = box.getCenter(new THREE.Vector3());
model.position.x += (model.position.x - center.x);
model.position.y += (model.position.y - center.y);
model.position.z += (model.position.z - center.z);
this.el.setObject3D('mesh', model);
}
});
93 changes: 93 additions & 0 deletions examples/mixed-reality/real-world-meshing/coffee-spawner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* global AFRAME */
AFRAME.registerComponent('coffee-spawner', {
schema: {
delay: {default: 250},
targetElementSelector: {default: ''}
},
init: function () {
var el = this.el;
this.delaySpawn = this.delaySpawn.bind(this);
this.cancelDelayedSpawn = this.cancelDelayedSpawn.bind(this);
this.onCollisionEnded = this.onCollisionEnded.bind(this);
this.onCollisionStarted = this.onCollisionStarted.bind(this);
this.el.addEventListener('pinchstarted', this.delaySpawn);
this.el.addEventListener('pinchended', this.cancelDelayedSpawn);
el.addEventListener('obbcollisionstarted', this.onCollisionStarted);
el.addEventListener('obbcollisionended', this.onCollisionEnded);
this.enabled = true;
},

delaySpawn: function (evt) {
this.pinchEvt = evt;
this.spawnDelay = this.data.delay;
},

cancelDelayedSpawn: function (evt) {
this.spawnDelay = undefined;
},

tick: function (time, delta) {
if (!this.spawnDelay) { return; }
this.spawnDelay -= delta;
if (this.spawnDelay <= 0) {
this.spawn(this.pinchEvt);
this.spawnDelay = undefined;
}
},

spawn: function (evt) {
var auxEuler = this.auxEuler;
var sceneEl = this.el.sceneEl;
var saucerEl = document.createElement('a-entity');
var cupEl = document.createElement('a-entity');
var wristRotation = evt.detail.wristRotation;
var animateScale = function (evt) {
evt.target.setAttribute('animation', {
property: 'scale',
from: {x: 0, y: 0, z: 0},
to: {x: 0.0015, y: 0.0015, z: 0.0015},
dur: 200
});
};

if (!this.enabled) { return; }
if (this.data.targetElementSelector && !this.targetEl) { return; }

saucerEl.setAttribute('gltf-model', '#coffee');
saucerEl.setAttribute('grabbable', '');
saucerEl.setAttribute('hide-model-parts', 'parts: coffee, cup, handle');
saucerEl.setAttribute('scale', '0.0015 0.0015 0.0015');
saucerEl.setAttribute('position', evt.detail.position);
saucerEl.addEventListener('loaded', animateScale);
sceneEl.appendChild(saucerEl);

cupEl.setAttribute('gltf-model', '#coffee');
cupEl.setAttribute('grabbable', '');
cupEl.setAttribute('hide-model-parts', 'parts: saucer');
cupEl.setAttribute('rotation', '0 90 0');
cupEl.setAttribute('scale', '0.0015 0.0015 0.0015');
cupEl.setAttribute('position', evt.detail.position);
cupEl.addEventListener('loaded', animateScale);
sceneEl.appendChild(cupEl);
},

onCollisionStarted: function (evt) {
var targetElementSelector = this.data.targetElementSelector;
var targetEl = targetElementSelector && this.el.sceneEl.querySelector(targetElementSelector);
if (targetEl === evt.detail.withEl) {
this.targetEl = targetEl;
return;
}
this.enabled = false;
},

onCollisionEnded: function (evt) {
var targetElementSelector = this.data.targetElementSelector;
var targetEl = targetElementSelector && this.el.sceneEl.querySelector(targetElementSelector);
if (targetEl === evt.detail.withEl) {
this.targetEl = undefined;
return;
}
this.enabled = true;
}
});
43 changes: 43 additions & 0 deletions examples/mixed-reality/real-world-meshing/hide-model-parts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* global AFRAME, THREE */
AFRAME.registerComponent('hide-model-parts', {
schema: {
parts: {type: 'array'}
},

update: function () {
this.hideParts = this.hideParts.bind(this);
this.el.addEventListener('model-loaded', this.hideParts);
},

hideParts: function () {
var part;
var parts = this.data.parts;
var model = this.el.getObject3D('mesh');
for (var i = 0; i < parts.length; i++) {
part = model.getObjectByName(parts[i]);
part.parent.remove(part);
}
},

/**
* Search for the part name and look for a mesh.
*/
selectFromModel: function (model) {
var mesh;
var part;
part = model.getObjectByName(this.data.part);
if (!part) {
console.error('[gltf-part] `' + this.data.part + '` not found in model.');
return;
}

mesh = part.getObjectByProperty('type', 'Mesh').clone(true);

if (this.data.buffer) {
mesh.geometry = mesh.geometry.toNonIndexed();
return mesh;
}
mesh.geometry = new THREE.Geometry().fromBufferGeometry(mesh.geometry);
return mesh;
}
});
50 changes: 50 additions & 0 deletions examples/mixed-reality/real-world-meshing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Real World Meshing (Mixed Reality) • A-Frame</title>
<meta name="description" content="Real World Meshing (Mixed Reality) • A-Frame">
<script src="../../dist/aframe-master.js"></script>
<script src="../../js/info-message.js"></script>
<script src="../../js/center-model.js"></script>
<script src="hide-model-parts.js"></script>
<script src="coffee-spawner.js"></script>
</head>
<body>
<a-scene
real-world-meshing="filter: table; meshesEnabled: false; planeMixin: xrplane"
obb-collider="showColliders: false"
renderer="colorManagement: true;"
xr-mode-ui="XRMode: ar"
info-message="htmlSrc: #messageText">
<a-assets>
<!-- Model
* title: Coffee
* source: https://sketchfab.com/3d-models/coffee-963a9e5d288c42509886c5efd4fedd3c
* author: Rosnandie Yikie (https://sketchfab.com/rosnandie.yikie) -->
<a-asset-item id="coffee"
src="https://cdn.aframe.io/examples/mixed-reality/real-world-meshing/models/ coffee.glb"
response-type="arraybuffer" crossorigin="anonymous"></a-asset-item>
<a-asset-item id="messageText" src="message.html"></a-asset-item>
<a-mixin
id="xrplane"
obb-collider
material="color: pink"
visible="false">
</a-mixin>
<img id="acoffeeshop" src="https://cdn.aframe.io/examples/mixed-reality/real-world-meshing/images/acoffeeshop.png" />
</a-assets>
<a-plane
material="src: #acoffeeshop; shader: flat"
height="10" width="17.7" position="0 1.5 -5.5" hide-on-enter-ar>
</a-plane>
<a-entity id="rightHand"
hand-tracking-grab-controls="hand: right"
coffee-spawner="targetElementSelector: [data-world-mesh=table]"></a-entity>
<a-entity id="leftHand"
hand-tracking-grab-controls="hand: left"
coffee-spawner="targetElementSelector: [data-world-mesh=table]"></a-entity>
</a-scene>
</body>
</html>

11 changes: 11 additions & 0 deletions examples/mixed-reality/real-world-meshing/message.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>
This demo requires a browser supporting the <a href="https://immersive-web.github.io/real-world-meshing/">WebXR Mesh Detection Module</a> and <a href="https://immersive-web.github.io/real-world-geometry/plane-detection.html">WebXR Plane Detection Module</a>
</p>

<p>
In AR mode the user can pinch over a suface labeled as table to spawn coffee cups. This requires the user to setup the environment: set boundaries, mesh and label at least one surface as table.
</p>

<p>
Frame model by <a href="https://sketchfab.com/3d-models/coffee-963a9e5d288c42509886c5efd4fedd3c">Rosnandie Yikie</a>
</p>
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require('./scene/inspector');
require('./scene/fog');
require('./scene/keyboard-shortcuts');
require('./scene/pool');
require('./scene/real-world-meshing');
require('./scene/reflection');
require('./scene/screenshot');
require('./scene/stats');
Expand Down
Loading

0 comments on commit 3764878

Please sign in to comment.