XRHandMeshModel.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { GLTFLoader } from '../loaders/GLTFLoader.js';
  2. import { clone } from '../utils/SkeletonUtils.js';
  3. const DEFAULT_HAND_PROFILE_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/';
  4. /**
  5. * Represents one of the hand model types {@link XRHandModelFactory} might produce
  6. * depending on the selected profile. `XRHandMeshModel` represents a hand with a
  7. * custom asset.
  8. *
  9. * @three_import import { XRHandMeshModel } from 'three/addons/webxr/XRHandMeshModel.js';
  10. */
  11. class XRHandMeshModel {
  12. /**
  13. * Constructs a new XR hand mesh model.
  14. *
  15. * @param {XRHandModel} handModel - The hand model.
  16. * @param {Group} controller - The WebXR controller.
  17. * @param {?string} path - The model path.
  18. * @param {XRHandedness} handedness - The handedness of the XR input source.
  19. * @param {?Loader} [loader=null] - The loader. If not provided, an instance of `GLTFLoader` will be used to load models.
  20. * @param {?Function} [onLoad=null] - A callback that is executed when a controller model has been loaded.
  21. * @param {?Object} [customCache=null] - An optional shared cache object for storing and reusing loaded assets across instances.
  22. */
  23. constructor( handModel, controller, path, handedness, loader = null, onLoad = null, customCache = null ) {
  24. /**
  25. * The WebXR controller.
  26. *
  27. * @type {Group}
  28. */
  29. this.controller = controller;
  30. /**
  31. * The hand model.
  32. *
  33. * @type {XRHandModel}
  34. */
  35. this.handModel = handModel;
  36. /**
  37. * An array of bones representing the bones
  38. * of the hand skeleton.
  39. *
  40. * @type {Array<Bone>}
  41. */
  42. this.bones = [];
  43. const pathToUse = path || DEFAULT_HAND_PROFILE_PATH;
  44. const processAsset = ( gltf ) => {
  45. const object = clone( gltf.scene.children[ 0 ] );
  46. this.handModel.add( object );
  47. const mesh = object.getObjectByProperty( 'type', 'SkinnedMesh' );
  48. mesh.frustumCulled = false;
  49. mesh.castShadow = true;
  50. mesh.receiveShadow = true;
  51. const joints = [
  52. 'wrist',
  53. 'thumb-metacarpal',
  54. 'thumb-phalanx-proximal',
  55. 'thumb-phalanx-distal',
  56. 'thumb-tip',
  57. 'index-finger-metacarpal',
  58. 'index-finger-phalanx-proximal',
  59. 'index-finger-phalanx-intermediate',
  60. 'index-finger-phalanx-distal',
  61. 'index-finger-tip',
  62. 'middle-finger-metacarpal',
  63. 'middle-finger-phalanx-proximal',
  64. 'middle-finger-phalanx-intermediate',
  65. 'middle-finger-phalanx-distal',
  66. 'middle-finger-tip',
  67. 'ring-finger-metacarpal',
  68. 'ring-finger-phalanx-proximal',
  69. 'ring-finger-phalanx-intermediate',
  70. 'ring-finger-phalanx-distal',
  71. 'ring-finger-tip',
  72. 'pinky-finger-metacarpal',
  73. 'pinky-finger-phalanx-proximal',
  74. 'pinky-finger-phalanx-intermediate',
  75. 'pinky-finger-phalanx-distal',
  76. 'pinky-finger-tip',
  77. ];
  78. joints.forEach( jointName => {
  79. const bone = object.getObjectByName( jointName );
  80. if ( bone !== undefined ) {
  81. bone.jointName = jointName;
  82. } else {
  83. console.warn( `Couldn't find ${jointName} in ${handedness} hand mesh` );
  84. }
  85. this.bones.push( bone );
  86. } );
  87. if ( onLoad ) onLoad( object );
  88. };
  89. const assetUrl = `${pathToUse}${handedness}.glb`;
  90. if ( customCache && customCache[ assetUrl ] ) {
  91. processAsset( customCache[ assetUrl ] );
  92. } else {
  93. if ( loader === null ) {
  94. loader = new GLTFLoader();
  95. loader.setPath( pathToUse );
  96. }
  97. loader.load( `${handedness}.glb`, gltf => {
  98. if ( customCache ) {
  99. customCache[ assetUrl ] = gltf;
  100. }
  101. processAsset( gltf );
  102. } );
  103. }
  104. }
  105. /**
  106. * Updates the mesh based on the tracked XR joints data.
  107. */
  108. updateMesh() {
  109. // XR Joints
  110. const XRJoints = this.controller.joints;
  111. for ( let i = 0; i < this.bones.length; i ++ ) {
  112. const bone = this.bones[ i ];
  113. if ( bone ) {
  114. const XRJoint = XRJoints[ bone.jointName ];
  115. if ( XRJoint.visible ) {
  116. const position = XRJoint.position;
  117. bone.position.copy( position );
  118. bone.quaternion.copy( XRJoint.quaternion );
  119. // bone.scale.setScalar( XRJoint.jointRadius || defaultRadius );
  120. }
  121. }
  122. }
  123. }
  124. }
  125. export { XRHandMeshModel };
粤ICP备19079148号