Custom Texture On Hand

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

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

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}
2 Likes

Thanks, @joshmao, it’s working.

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.