# Custom Texture On Hand

**URL:** <https://forum.8thwall.com/t/custom-texture-on-hand/1491>\
**Category:** Technical Support\
**Tags:** hand-tracking\
**Created:** [May 28, 2024, 11:40am UTC](https://forum.8thwall.com/t/custom-texture-on-hand/1491 "2024-05-28T11:40:08Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![AliveNow\_Inc](https://avatars.discourse-cdn.com/v4/letter/a/db5fbb/32.png) [@AliveNow\_Inc](https://forum.8thwall.com/u/AliveNow_Inc)\
**Post date:** [May 28, 2024, 11:40am UTC](https://forum.8thwall.com/t/custom-texture-on-hand/1491/1 "2024-05-28T11:40:08Z")

</div>

How can we use custom textures on hand? I have tried with threejs. Only one color is showing instead of texture.

---

<div class="post-metadata">

**Author:** ![joshmao](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.8thwall.com/joshmao/32/660_2.png) [@joshmao](https://forum.8thwall.com/u/joshmao)\
**Post date:** [May 28, 2024, 10:30pm UTC](https://forum.8thwall.com/t/custom-texture-on-hand/1491/2 "2024-05-28T22:30:30Z")

</div>

You can use custom textures on the hand by ingesting the UVs and creating a hand mesh from it and then texturing the hand mesh.

We do this automatically in our Hand Mesh primitive in A-Frame but you would have to do this manually in three.js

```auto
const handMesh = (modelGeometry, material, wireframe, uvOrientation) => {
  let handKind = 2
  const geometry = new THREE.BufferGeometry()

  // Fill geometry with default vertices.
  const vertices = new Float32Array(modelGeometry.pointsPerDetection * 3)
  geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))

  // Fill geometry with default normals.
  const normals = new Float32Array(modelGeometry.pointsPerDetection * 3)
  geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3))

  // By default, add right hand UVs to the geometry
  const rightUvs = new Float32Array(modelGeometry.rightUvs.length * 2)
  for (let i = 0; i < modelGeometry.pointsPerDetection; i++) {
    rightUvs[2 * i] = modelGeometry.rightUvs[i].u
    rightUvs[2 * i + 1] = modelGeometry.rightUvs[i].v
  }
  const rightUvBuffer = new THREE.BufferAttribute(rightUvs, 2)

  const leftUvs = new Float32Array(modelGeometry.leftUvs.length * 2)
  for (let i = 0; i < modelGeometry.pointsPerDetection; i++) {
    leftUvs[2 * i] = modelGeometry.leftUvs[i].u
    leftUvs[2 * i + 1] = modelGeometry.leftUvs[i].v
  }
  const leftUvBuffer = new THREE.BufferAttribute(leftUvs, 2)

  // Grab left and right hand indices
  const rightIndices = new Array(modelGeometry.rightIndices.length * 3)
  for (let i = 0; i < modelGeometry.rightIndices.length; ++i) {
    rightIndices[i * 3] = modelGeometry.rightIndices[i].a
    rightIndices[i * 3 + 1] = modelGeometry.rightIndices[i].b
    rightIndices[i * 3 + 2] = modelGeometry.rightIndices[i].c
  }

  const leftIndices = new Array(modelGeometry.leftIndices.length * 3)
  for (let i = 0; i < modelGeometry.leftIndices.length; ++i) {
    leftIndices[i * 3] = modelGeometry.leftIndices[i].a
    leftIndices[i * 3 + 1] = modelGeometry.leftIndices[i].b
    leftIndices[i * 3 + 2] = modelGeometry.leftIndices[i].c
  }

  // set UV and indices type based on hand mesh orientation
  let uv
  if (uvOrientation === 'left') {
    uv = leftUvBuffer
    geometry.setIndex(leftIndices)
  } else if (uvOrientation === 'right') {
    uv = rightUvBuffer
    geometry.setIndex(rightIndices)
  }
  geometry.setAttribute('uv', uv)

  if (wireframe) {
    material.wireframe = true
  }

  const mesh = new THREE.Mesh(geometry, material)

  const show = ({detail}) => {
    // set indices based on handKind
    if (handKind !== detail.handKind) {
      handKind = detail.handKind
      if (handKind === 1) {
        mesh.geometry.setIndex(leftIndices)
      } else {
        mesh.geometry.setIndex(rightIndices)
      }
    }

    // Update vertex positions.
    for (let i = 0; i < detail.vertices.length; ++i) {
      vertices[i * 3] = detail.vertices[i].x
      vertices[i * 3 + 1] = detail.vertices[i].y
      vertices[i * 3 + 2] = detail.vertices[i].z
    }
    mesh.geometry.attributes.position.needsUpdate = true

    // Update vertex normals.
    for (let i = 0; i < detail.normals.length; ++i) {
      normals[i * 3] = detail.normals[i].x
      normals[i * 3 + 1] = detail.normals[i].y
      normals[i * 3 + 2] = detail.normals[i].z
    }
    mesh.geometry.attributes.normal.needsUpdate = true

    // make it so frustum doesn't cull mesh when hand is close to camera
    mesh.frustumCulled = false
    mesh.visible = true
  }

  const hide = () => {
    mesh.visible = false
  }

  return {
    mesh,
    show,
    hide,
  }
}

const handMeshComponent = {
  schema: {
    'material-resource': {type: 'string'},
    'wireframe': {type: 'boolean', default: true},
    'uv-orientation': {type: 'string', default: 'right'},
  },
  init() {
    this.handMesh = null

    const beforeRun = ({detail}) => {
      let material

      if (this.el.getAttribute('material')) {
        material = this.el.components.material.material
      } else if (this.data['material-resource']) {
        material = this.el.sceneEl.querySelector(this.data['material-resource']).material
      } else {
        material = new THREE.MeshBasicMaterial(
          {color: '#7611B6', opacity: 0.5, transparent: true}
        )
      }

      this.handMesh = handMesh(detail, material, this.data.wireframe, this.data['uv-orientation'])
      this.el.setObject3D('mesh', this.handMesh.mesh)

      this.el.emit('model-loaded')
    }

    const show = (event) => {
      this.handMesh.show(event)
      this.el.object3D.visible = true
    }

    const hide = () => {
      this.handMesh.hide()
      this.el.object3D.visible = false
    }

    this.el.sceneEl.addEventListener('xrhandloading', beforeRun)
    this.el.sceneEl.addEventListener('xrhandfound', show)
    this.el.sceneEl.addEventListener('xrhandupdated', show)
    this.el.sceneEl.addEventListener('xrhandlost', hide)
  },
  update() {
    if (!this.handMesh) {
      return
    }

    let material
    if (this.el.getAttribute('material')) {
      material = this.el.components.material.material
    } else if (this.data['material-resource']) {
      material = this.el.sceneEl.querySelector(this.data['material-resource']).material
    } else {
      material = new THREE.MeshBasicMaterial({color: '#7611B6', opacity: 0.5, transparent: true})
    }
    this.handMesh.mesh.material = material
  },

}

export {handMeshComponent}

```

---

<div class="post-metadata">

**Author:** ![AliveNow\_Inc](https://avatars.discourse-cdn.com/v4/letter/a/db5fbb/32.png) [@AliveNow\_Inc](https://forum.8thwall.com/u/AliveNow_Inc)\
**Post date:** [May 29, 2024, 6:57am UTC](https://forum.8thwall.com/t/custom-texture-on-hand/1491/3 "2024-05-29T06:57:13Z")

</div>

Thanks, @joshmao, it’s working.

---

<div class="post-metadata">

**Author:** ![Mal\_Duffin](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.8thwall.com/mal_duffin/32/124_2.png) [@Mal\_Duffin](https://forum.8thwall.com/u/Mal_Duffin)\
**Post date:** [June 6, 2024, 1:07pm UTC](https://forum.8thwall.com/t/custom-texture-on-hand/1491/4 "2024-06-06T13:07:11Z")

</div>

> We do this automatically in our Hand Mesh primitive in A-Frame

Is there a flag to disable the automatic adding of hand controls, so that users can add their own setup using a-frame?

When I use a-frame directly and do stuff like parent items to the camera ( headset ) by adding an \<a-entity within the \<a-camera element, it works great by default. When I do this in 8th Wall, weird stuff happens like the items get parented to the controller instead of the headset.

Having the hands / controller set up is a great default option, but if anyone wants to do a custom setup, this needs to be disabled in order for them to make the required changes.
