Skip to content

Commit

Permalink
Merge pull request #362 from networked-aframe/doc-networked-hand-cont…
Browse files Browse the repository at this point in the history
…rols

Less confusing doc with the new introduced networked-hand-controls
  • Loading branch information
vincentfretin committed Sep 13, 2022
2 parents 32651f3 + 8d0624d commit 2989fbe
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 53 deletions.
74 changes: 58 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,11 @@ Templates must only have one root element. When `attachTemplateToLocal` is set t
</a-entity>
```

| Parameter | Description | Default
| -------- | ------------ | --------------
| template | A css selector to a template tag stored in `<a-assets>` | ''
| attachTemplateToLocal | Does not attach the template for the local user when set to false. This is useful when there is different behavior locally and remotely. | true
| persistent | On remote creator (not owner) disconnect, attempts to take ownership of persistent entities rather than delete them | false

| Property | Description | Default Value |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| template | A css selector to a template tag stored in `<a-assets>` | '' |
| attachTemplateToLocal | Does not attach the template for the local user when set to false. This is useful when there is different behavior locally and remotely. | true |
| persistent | On remote creator (not owner) disconnect, attempts to take ownership of persistent entities rather than delete them | false |

### Deleting Networked Entities

Expand Down Expand Up @@ -353,30 +352,73 @@ To sync nested templates setup your HTML nodes like so:

In this example the head/camera, left and right hands will spawn their own templates which will be networked independently of the root player. Note: this parent-child relationship only works between one level, ie. a child entity's direct parent must have the `networked` component.

You need to define your left and right hand templates yourself to show hand models for the other users. Only the position and rotation will be synced to the other users. To sync the hand gesture, see the `networked-hand-controls` component below.

### Tracked Controllers w/ Synced Gestures

NAF now allows easily adding hand models that show gestures matching to which buttons are touched--so you can point and give a thumbs up or make a fist to other people in the room.
This is a much simpler alternative to the above.
NAF allows easily adding hand models visible to the others that show gestures matching to which buttons are touched--so you can point and give a thumbs up or make a fist to other people in the room.

All you have to do is use the built in `networked-hand-controls` component, by adding these two lines as children of your camera rig:
All you have to do is use the built in `networked-hand-controls` component, by adding these two entities as children of your camera rig:

```html
<a-entity id="my-tracked-left-hand" networked-hand-controls="hand:left;"></a-entity>
<a-entity id="my-tracked-right-hand" networked-hand-controls="hand:right;"></a-entity>
<a-entity
id="my-tracked-left-hand"
networked-hand-controls="hand:left"
networked="template:#left-hand-default-template"
></a-entity>
<a-entity
id="my-tracked-right-hand"
networked-hand-controls="hand:right"
networked="template:#right-hand-default-template"
></a-entity
```

To see a working demo, check out the [Glitch NAF Tracked Controllers Example](https://naf-examples.glitch.me/tracked-controllers.html).

The public schema properties you can set are:

| ---- | color | hand | handModelStyle | customHandModelURL |
| ---- | ----- | ---- | -------------- | ------------ |
| info | will be set as material color | - | available built-in models from A-Frame | optional custom hand model url |
| default | 'white' | 'left' | 'highPoly' | '' |
| type | 'color' | 'string' | 'string' | 'string' |
| oneOf | N/A | ['right', 'left'] | ['highPoly', 'lowPoly', 'toon', 'controller'] | N/A |
| Property | Description | Default Value | Values |
| ------------------ | ------------------------------------------- | ------------- | ----------------------------------- |
| color | Will be set as material color | white |
| hand | Specify if entity is for left or right hand | left | left, right |
| handModelStyle | Available built-in models from A-Frame | highPoly | highPoly, lowPoly, toon, controller |
| customHandModelURL | Optional custom hand model url | | |

Note the 'controller' option--that will use a model of the controller itself, automatically set correctly according to your platform--it will also broadcast model-supported button mesh updates. (Unfortunately, there's currently a bug with the Quest 2 model button meshes, so that one doesn't show any updates.)

The `networked-hand-controls` is replacing completely `hand-controls`, don't use both.
If you use the networked component as described above, you don't need to define the template and the networked schema for each hand.
Default templates and networked schemas are already defined as follow:

```html
<template id="left-hand-default-template">
<a-entity networked-hand-controls="hand:left"></a-entity>
</template>
<template id="right-hand-default-template">
<a-entity networked-hand-controls="hand:right"></a-entity>
</template>
```

```javascript
NAF.schemas.add({
template: '#left-hand-default-template'
components: [
'position',
'rotation',
'networked-hand-controls'
]
});
NAF.schemas.add({
template: '#right-hand-default-template'
components: [
'position',
'rotation',
'networked-hand-controls'
]
});
```

### Sending Custom Messages

```javascript
Expand Down
11 changes: 8 additions & 3 deletions examples/tracked-controllers.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@
visible="false"
>
</a-entity>
<!-- here we add the user's local hands! These two lines are all that is needed. -->
<a-entity id="my-tracked-left-hand" networked-hand-controls="hand:left; color:gold;"></a-entity>
<!-- here we add the user's local hands! These two entities are all that is needed. -->
<a-entity
id="my-tracked-left-hand"
networked-hand-controls="hand:left;color:gold;"
networked="template:#left-hand-default-template"
></a-entity>
<a-entity
id="my-tracked-right-hand"
networked-hand-controls="hand:right; handModelStyle: controller;"
networked-hand-controls="hand:right;handModelStyle:controller;"
networked="template:#right-hand-default-template"
></a-entity>
</a-entity>
</a-scene>
Expand Down
79 changes: 45 additions & 34 deletions src/components/networked-hand-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ function addHandTemplate(hand) {
let templateOuter = document.createElement('template');
let templateInner = document.createElement('a-entity');

templateOuter.id = `${hand}-hand-template`;
templateInner.setAttribute('networked-hand-controls',`hand: ${hand}`);
templateOuter.id = `${hand}-hand-default-template`;
templateInner.setAttribute('networked-hand-controls', `hand: ${hand}`);

templateOuter.appendChild(templateInner);

NAF.schemas.schemaDict[`#${hand}-hand-template`] = {
template: `#${hand}-hand-template`,
const refTemplateId = `#${templateOuter.id}`;
NAF.schemas.schemaDict[refTemplateId] = {
template: refTemplateId,
components: [
'position',
'rotation',
'networked-hand-controls',
]
};
NAF.schemas.templateCache[`#${hand}-hand-template`] = templateOuter;
NAF.schemas.templateCache[refTemplateId] = templateOuter;
}
["left","right"].forEach(addHandTemplate);

Expand Down Expand Up @@ -68,39 +69,45 @@ AFRAME.registerComponent('networked-hand-controls', {

init() {
this.setup();
this.el.setAttribute('networked', 'template', `#${this.data.hand}-hand-template`);
this.el.setAttribute('networked', 'attachTemplateToLocal', true);

this.local = this.el.components.networked.createdByMe();

if (this.local) {
for (const evtName in this.buttonEventMap) {
this.eventFunctionMap[this.data.hand][evtName] = this.handleButton.bind(this, ...this.buttonEventMap[evtName]);
}
for (const evtName in this.visibleListeners) {
this.eventFunctionMap[this.data.hand][evtName] = this.visibleListeners[evtName].bind(this);
}
}
else {
this.el.classList.add('naf-remote-hand');
}
this.rendererSystem = this.el.sceneEl.systems.renderer;

if (this.data.handModelStyle !== "controller") {
this.addHandModel();
}
if (this.local) {
this.addControllerComponents(this.data.handModelStyle == "controller");
}

NAF.utils.getNetworkedEntity(this.el).then((networkedEl) => {
// Here networkedEl may be different than this.el if we don't use nested
// networked components for hands.
this.local = networkedEl.components.networked.createdByMe();
}).catch(() => {
this.local = true;
}).then(() => {
if (this.local) {
this.addControllerComponents(this.data.handModelStyle === "controller");
// Bind all functions, the listeners are only registered later for local avatar.
for (const evtName in this.buttonEventMap) {
this.eventFunctionMap[this.data.hand][evtName] = this.handleButton.bind(this, ...this.buttonEventMap[evtName]);
}
for (const evtName in this.visibleListeners) {
this.eventFunctionMap[this.data.hand][evtName] = this.visibleListeners[evtName].bind(this);
}
} else {
// Adding a class to easily see what the entity is in the aframe
// inspector. This is shown in the class field in the panel after you
// click the entity.
this.el.classList.add('naf-remote-hand');
}
});
},

play() {
if (this.local) {
this.addEventListeners();
this.addEventListeners();
}
},

pause() {
if (this.local) {
if (this.local) {
this.removeEventListeners();
}
},
Expand All @@ -119,7 +126,7 @@ AFRAME.registerComponent('networked-hand-controls', {
oldData.handModelStyle !== this.data.handModelStyle
) {
// first, remove old model
this.el.removeObject3D(this.str.mesh);
if (this.getMesh()) this.el.removeObject3D(this.str.mesh);
['gltf-model','obj-model'].forEach(modelComponent => {
if (this.el.components[modelComponent]) {
this.el.removeAttribute(modelComponent);
Expand All @@ -134,12 +141,14 @@ AFRAME.registerComponent('networked-hand-controls', {
// this.el.setAttribute(this.data.controllerComponent, 'model', true)

// so we first remove the controller component
if (this.el.components[this.data.controllerComponent]) {
if (this.data.controllerComponent && this.el.components[this.data.controllerComponent]) {
this.el.removeAttribute(this.data.controllerComponent);
}

if (!this.local) {
this.injectRemoteControllerModel();
if (!this.injectedController && this.data.controllerComponent && this.data.webxrControllerProfiles[0]) {
this.injectRemoteControllerModel();
}
}
else {
this.addControllerComponents(true);
Expand Down Expand Up @@ -201,17 +210,19 @@ AFRAME.registerComponent('networked-hand-controls', {
this.el.setObject3D(this.str.mesh, newMesh);

const handMaterial = newMesh.children[1].material;
handMaterial.color = new THREE.Color(this.data.handColor);
handMaterial.color = new THREE.Color(this.data.color);
this.rendererSystem.applyColorCorrection(handMaterial.color);
newMesh.position.set(0, 0, 0);
newMesh.rotation.set(0, 0, handModelOrientation);

this.updateHandMeshColor();
});
},

updateHandMeshColor() {
this.getMesh().children[1].material.color.set(this.data.color);
this.el.sceneEl.systems.renderer.applyColorCorrection(this.getMesh().children[1].material.color);
const mesh = this.getMesh();
if (!mesh) return;
const handMaterial = mesh.children[1].material;
handMaterial.color.set(this.data.color);
this.rendererSystem.applyColorCorrection(handMaterial.color);
},

controllerComponents: [
Expand Down

0 comments on commit 2989fbe

Please sign in to comment.