-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
19 changed files
with
898 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
title: hand-tracking-controls | ||
type: components | ||
layout: docs | ||
parent_section: components | ||
source_code: src/components/hand-tracking-controls.js | ||
examples: [] | ||
--- | ||
|
||
[webxrhandinput]: https://immersive-web.github.io/webxr-hand-input/ | ||
|
||
Use `hand-tracking-controls` to integrate [hand tracked input][webxrhandinput] in your application. The component provides a visual representation of the hand and basic gesture recognition. It can be used along tracked controllers (e.g: oculus-touch-controls) for applications requiring multiple input methods. Component is only active when the browser and underlying system starts tracking the user's hands. | ||
|
||
## Example | ||
|
||
```html | ||
<a-entity id="leftHand" hand-tracking-controls="hand: left;"></a-entity> | ||
<a-entity id="rightHand" hand-tracking-controls="hand: right;"></a-entity> | ||
``` | ||
|
||
## Properties | ||
|
||
| Property | Description | Default Value | | ||
|----------------|----------------------------------------------------------------------------------------|---------------| | ||
| hand | The hand that will be tracked (i.e., right, left). | left | | ||
| modelColor | Color of hand material. | white | | ||
| modelStyle | Mesh representing the hand or dots matching the joints | mesh | ||
|
||
|
||
## Events | ||
|
||
| Event Name | Description | | ||
| ---------- | ----------- | | ||
| pinchstarted | The pinch gesture has started. World coordinates passed as event detail. | | ||
| pinchended | The pinch gesture has ended. World coordinates passed as event detail. | | ||
| pinchmoved | The hand moved while making the pinch gesture. Useful for interactions that track the hand while the gesture is engaged. World coordinates passed as event detail. | | ||
|
||
## Assets | ||
|
||
- [Left hand low poly model](https://cdn.aframe.io/controllers/oculus-hands/unity/left.glb) | ||
- [Right hand low poly model](https://cdn.aframe.io/controllers/oculus-hands/unity/right.glb) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* global AFRAME */ | ||
AFRAME.registerComponent('button', { | ||
schema: { | ||
label: {default: 'label'}, | ||
width: {default: 0.11}, | ||
toggable: {default: false} | ||
}, | ||
init: function () { | ||
var el = this.el; | ||
var labelEl = this.labelEl = document.createElement('a-entity'); | ||
|
||
this.color = '#3a50c5'; | ||
el.setAttribute('geometry', { | ||
primitive: 'box', | ||
width: this.data.width, | ||
height: 0.05, | ||
depth: 0.04 | ||
}); | ||
|
||
el.setAttribute('material', {color: this.color}); | ||
el.setAttribute('pressable', ''); | ||
|
||
labelEl.setAttribute('position', '0 0 0.02'); | ||
labelEl.setAttribute('text', { | ||
value: this.data.label, | ||
color: 'white', | ||
align: 'center' | ||
}); | ||
|
||
labelEl.setAttribute('scale', '0.75 0.75 0.75'); | ||
this.el.appendChild(labelEl); | ||
|
||
this.bindMethods(); | ||
this.el.addEventListener('stateadded', this.stateChanged); | ||
this.el.addEventListener('stateremoved', this.stateChanged); | ||
this.el.addEventListener('pressedstarted', this.onPressedStarted); | ||
this.el.addEventListener('pressedended', this.onPressedEnded); | ||
}, | ||
|
||
bindMethods: function () { | ||
this.stateChanged = this.stateChanged.bind(this); | ||
this.onPressedStarted = this.onPressedStarted.bind(this); | ||
this.onPressedEnded = this.onPressedEnded.bind(this); | ||
}, | ||
|
||
update: function (oldData) { | ||
if (oldData.label !== this.data.label) { | ||
this.labelEl.setAttribute('text', 'value', this.data.label); | ||
} | ||
}, | ||
|
||
stateChanged: function () { | ||
var color = this.el.is('pressed') ? 'green' : this.color; | ||
this.el.setAttribute('material', {color: color}); | ||
}, | ||
|
||
onPressedStarted: function () { | ||
var el = this.el; | ||
el.setAttribute('material', {color: 'green'}); | ||
el.emit('click'); | ||
if (this.data.togabble) { | ||
if (el.is('pressed')) { | ||
el.removeState('pressed'); | ||
} else { | ||
el.addState('pressed'); | ||
} | ||
} | ||
}, | ||
|
||
onPressedEnded: function () { | ||
if (this.el.is('pressed')) { return; } | ||
this.el.setAttribute('material', {color: this.color}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* global AFRAME */ | ||
AFRAME.registerComponent('color-change', { | ||
schema: { | ||
color: { default: 'green' } | ||
}, | ||
|
||
init: function () { | ||
this.bindMethods(); | ||
this.el.addEventListener('pinchedstarted', this.onPinchedStarted); | ||
this.el.addEventListener('pinchedended', this.onPinchedEnded); | ||
}, | ||
|
||
bindMethods: function () { | ||
this.onPinchedStarted = this.onPinchedStarted.bind(this); | ||
this.onPinchedEnded = this.onPinchedEnded.bind(this); | ||
}, | ||
|
||
onPinchedStarted: function () { | ||
this.originalColor = this.originalColor || this.el.getAttribute('material').color; | ||
this.el.setAttribute('material', 'color', this.data.color); | ||
}, | ||
|
||
onPinchedEnded: function () { | ||
this.el.setAttribute('material', 'color', this.originalColor); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* global AFRAME */ | ||
AFRAME.registerComponent('event-manager', { | ||
|
||
init: function () { | ||
this.bindMethods(); | ||
|
||
this.boxGeometryEl = document.querySelector('#boxGeometry'); | ||
this.sphereGeometryEl = document.querySelector('#sphereGeometry'); | ||
this.torusGeometryEl = document.querySelector('#torusGeometry'); | ||
|
||
this.boxButtonEl = document.querySelector('#boxButton'); | ||
this.sphereButtonEl = document.querySelector('#sphereButton'); | ||
this.torusButtonEl = document.querySelector('#torusButton'); | ||
this.darkModeButtonEl = document.querySelector('#darkModeButton'); | ||
|
||
this.buttonToGeometry = { | ||
'boxButton': this.boxGeometryEl, | ||
'sphereButton': this.sphereGeometryEl, | ||
'torusButton': this.torusGeometryEl | ||
}; | ||
|
||
this.boxButtonEl.addEventListener('click', this.onClick); | ||
this.sphereButtonEl.addEventListener('click', this.onClick); | ||
this.torusButtonEl.addEventListener('click', this.onClick); | ||
this.darkModeButtonEl.addEventListener('click', this.onClick); | ||
this.boxButtonEl.addState('pressed'); | ||
}, | ||
|
||
bindMethods: function () { | ||
this.onClick = this.onClick.bind(this); | ||
}, | ||
|
||
onClick: function (evt) { | ||
var targetEl = evt.target; | ||
if (targetEl === this.boxButtonEl || | ||
targetEl === this.sphereButtonEl || | ||
targetEl === this.torusButtonEl) { | ||
this.boxButtonEl.removeState('pressed'); | ||
this.sphereButtonEl.removeState('pressed'); | ||
this.torusButtonEl.removeState('pressed'); | ||
this.boxGeometryEl.object3D.visible = false; | ||
this.sphereGeometryEl.object3D.visible = false; | ||
this.torusGeometryEl.object3D.visible = false; | ||
this.buttonToGeometry[targetEl.id].object3D.visible = true; | ||
} | ||
|
||
if (targetEl === this.darkModeButtonEl) { | ||
if (this.el.sceneEl.is('starry')) { | ||
targetEl.setAttribute('button', 'label', 'Dark Mode'); | ||
this.el.sceneEl.setAttribute('environment', {preset: 'default'}); | ||
this.el.sceneEl.removeState('starry'); | ||
} else { | ||
targetEl.setAttribute('button', 'label', 'Light Mode'); | ||
this.el.sceneEl.setAttribute('environment', {preset: 'starry'}); | ||
this.el.sceneEl.addState('starry'); | ||
} | ||
} else { | ||
targetEl.addState('pressed'); | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Hand Tracking! • A-Frame</title> | ||
<meta name="description" content="Hand Tracking! • A-Frame"> | ||
<script src="../../../dist/aframe-master.js"></script> | ||
<script src="https://unpkg.com/aframe-environment-component@1.1.0/dist/aframe-environment-component.min.js"></script> | ||
<script src="pinchable.js"></script> | ||
<script src="color-change.js"></script> | ||
<script src="slider.js"></script> | ||
<script src="size-change.js"></script> | ||
<script src="button.js"></script> | ||
<script src="menu.js"></script> | ||
<script src="pressable.js"></script> | ||
<script src="event-manager.js"></script> | ||
</head> | ||
<body> | ||
<a-scene environment> | ||
<a-entity | ||
id="boxGeometry" | ||
position="0 2.0 -1" | ||
rotation="45 45 0" | ||
geometry="primitive: box; height: 0.6; width: 0.6; depth: 0.6" | ||
material="color: #ff4b5c" | ||
size-change | ||
scale="0.5 0.5 0.5" | ||
></a-entity> | ||
<a-entity | ||
id="torusGeometry" | ||
position="0 2.0 -1" | ||
rotation="45 45 0" | ||
geometry="primitive: torus; radius: 0.4; radius-tubular: 0.06" | ||
material="color: #d2e603" | ||
size-change | ||
visible="false" | ||
scale="0.5 0.5 0.5" | ||
></a-entity> | ||
<a-entity | ||
id="sphereGeometry" | ||
position="0 2.0 -1" | ||
rotation="45 45 0" | ||
geometry="primitive: sphere; radius: 0.6;" | ||
material="color: #fcdab7" | ||
size-change | ||
visible="false" | ||
scale="0.5 0.5 0.5" | ||
></a-entity> | ||
<a-entity id="menu" menu position="0 1.5 -0.5" rotation="-45 0 0" event-manager> | ||
<a-entity slider position="0 0.10 0"></a-entity> | ||
<a-entity id="sphereButton" button="label: sphere" position="-0.15 0 0"></a-entity> | ||
<a-entity id="boxButton" button="label: box" position="0 0 0"></a-entity> | ||
<a-entity id="torusButton"button="label: torus" position="0.15 0 0"></a-entity> | ||
<a-entity id="darkModeButton" button="label: Dark Mode; width: 0.20; toggable: true" position="0 -0.10 0"></a-entity> | ||
</a-entity> | ||
<a-entity hand-tracking-controls="hand: left"></a-entity> | ||
<a-entity hand-tracking-controls="hand: right"></a-entity> | ||
</a-scene> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* global AFRAME */ | ||
AFRAME.registerComponent('menu', { | ||
init: function () { | ||
var el = this.el; | ||
var menuBackGroundEl = document.createElement('a-entity'); | ||
menuBackGroundEl.setAttribute('geometry', { | ||
primitive: 'box', | ||
width: 0.6, | ||
height: 0.40, | ||
depth: 0.01 | ||
}); | ||
menuBackGroundEl.setAttribute('material', { | ||
color: 'gray' | ||
}); | ||
menuBackGroundEl.setAttribute('position', '0 0 -0.025'); | ||
el.appendChild(menuBackGroundEl); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* global AFRAME, THREE */ | ||
AFRAME.registerComponent('pinchable', { | ||
schema: { | ||
pinchDistance: { default: 0.1 } | ||
}, | ||
|
||
init: function () { | ||
var sceneEl = this.el.sceneEl; | ||
this.worldPosition = new THREE.Vector3(); | ||
this.bindMethods(); | ||
this.pinched = false; | ||
sceneEl.addEventListener('pinchstarted', this.onPinchStarted); | ||
sceneEl.addEventListener('pinchended', this.onPinchEnded); | ||
sceneEl.addEventListener('pinchmoved', this.onPinchMoved); | ||
}, | ||
|
||
bindMethods: function () { | ||
this.onPinchStarted = this.onPinchStarted.bind(this); | ||
this.onPinchEnded = this.onPinchEnded.bind(this); | ||
this.onPinchMoved = this.onPinchMoved.bind(this); | ||
}, | ||
|
||
onPinchStarted: function (evt) { | ||
var pinchDistance = this.calculatePinchDistance(evt.detail.position); | ||
if (pinchDistance < this.data.pinchDistance) { | ||
this.el.emit('pinchedstarted'); | ||
this.pinched = true; | ||
} | ||
}, | ||
|
||
calculatePinchDistance: function (pinchWorldPosition) { | ||
var el = this.el; | ||
var worldPosition = this.worldPosition; | ||
var pinchDistance; | ||
|
||
worldPosition.copy(el.object3D.position); | ||
el.object3D.parent.updateMatrixWorld(); | ||
el.object3D.parent.localToWorld(worldPosition); | ||
|
||
pinchDistance = worldPosition.distanceTo(pinchWorldPosition); | ||
|
||
return pinchDistance; | ||
}, | ||
|
||
onPinchEnded: function (evt) { | ||
if (this.pinched) { | ||
this.pinched = false; | ||
this.el.emit('pinchedended'); | ||
} | ||
}, | ||
|
||
onPinchMoved: function (evt) { | ||
var el = this.el; | ||
var pinchDistance = this.calculatePinchDistance(evt.detail.position); | ||
if (!this.pinched) { return; } | ||
if (pinchDistance < this.data.pinchDistance) { | ||
el.emit('pinchedmoved', evt.detail); | ||
} else { | ||
this.pinched = false; | ||
el.emit('pinchedended'); | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* global AFRAME, THREE */ | ||
AFRAME.registerComponent('pressable', { | ||
schema: { | ||
pressDistance: { default: 0.06 } | ||
}, | ||
|
||
init: function () { | ||
this.worldPosition = new THREE.Vector3(); | ||
this.handEls = document.querySelectorAll('[hand-tracking-controls]'); | ||
this.pressed = false; | ||
}, | ||
|
||
tick: function () { | ||
var handEls = this.handEls; | ||
var handEl; | ||
var distance; | ||
for (var i = 0; i < handEls.length; i++) { | ||
handEl = handEls[i]; | ||
distance = this.calculateFingerDistance(handEl.components['hand-tracking-controls'].indexTipPosition); | ||
if (distance < this.data.pressDistance) { | ||
if (!this.pressed) { this.el.emit('pressedstarted'); } | ||
this.pressed = true; | ||
return; | ||
} | ||
} | ||
if (this.pressed) { this.el.emit('pressedended'); } | ||
this.pressed = false; | ||
}, | ||
|
||
calculateFingerDistance: function (fingerPosition) { | ||
var el = this.el; | ||
var worldPosition = this.worldPosition; | ||
|
||
worldPosition.copy(el.object3D.position); | ||
el.object3D.parent.updateMatrixWorld(); | ||
el.object3D.parent.localToWorld(worldPosition); | ||
|
||
return worldPosition.distanceTo(fingerPosition); | ||
} | ||
}); |
Oops, something went wrong.