Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Less confusing doc with the new introduced networked-hand-controls #362

Merged
merged 8 commits into from
Sep 13, 2022
66 changes: 50 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,65 @@ 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 networked-hand-controls="hand:left" networked="template:#left-hand-default-template"></a-entity>
<a-entity 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
66 changes: 35 additions & 31 deletions src/components/networked-hand-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ 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`,
NAF.schemas.schemaDict[`#${hand}-hand-default-template`] = {
template: `#${hand}-hand-default-template`,
components: [
'position',
'rotation',
'networked-hand-controls',
]
};
NAF.schemas.templateCache[`#${hand}-hand-template`] = templateOuter;
NAF.schemas.templateCache[`#${hand}-hand-default-template`] = templateOuter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have done this, but good to fix now: this should probably be

NAF.schemas.templateCache[`#${templateOuter.id}] = templateOuter;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I introduced

const refTemplateId = `#${templateOuter.id}`;

and used it everywhere.

}
["left","right"].forEach(addHandTemplate);

Expand Down Expand Up @@ -68,39 +68,39 @@ 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by removing this condition, we do the binding unnecessarily for networked entities that don't respond to events. not a huge deal, but it's an unnecessary inefficiency. Since you moved/improved the this.local detection, you could move these method bindings to be async / done after line 91.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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);
}
for (const evtName in this.buttonEventMap) {
this.eventFunctionMap[this.data.hand][evtName] = this.handleButton.bind(this, ...this.buttonEventMap[evtName]);
}
else {
this.el.classList.add('naf-remote-hand');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I ask why we remove this? this makes it easier to view in the a-frame inspector at a glance, if nothing else. I am a fan of things getting classes and IDs where possible to make things more viewable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see it used anywhere so I removed it. I almost never use the aframe inspector, so I didn't see the value of it, but you make a good point. I'll put it back.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for (const evtName in this.visibleListeners) {
this.eventFunctionMap[this.data.hand][evtName] = this.visibleListeners[evtName].bind(this);
}

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");
}
});
},

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

pause() {
if (this.local) {
if (this.local) {
this.removeEventListeners();
}
},
Expand All @@ -119,7 +119,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 +134,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 +203,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.el.sceneEl.systems.renderer.applyColorCorrection(handMaterial.color);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why duplicate this code instead of calling updateHandMeshColor() ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have the handMaterial variable here and we set the color already, calling updateHandMeshColor() to get again the mesh, setting the color that didn't change just to convert the color with applyColorCorrection seems the real duplication to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defined this.rendererSystem is init for better readability.

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.el.sceneEl.systems.renderer.applyColorCorrection(handMaterial.color);
},

controllerComponents: [
Expand Down