WebXRHandTracking.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js";
  2. import { WebXRFeatureName, WebXRFeaturesManager } from "../webXRFeaturesManager.js";
  3. import { Matrix, Quaternion } from "../../Maths/math.vector.js";
  4. import { PhysicsImpostor } from "../../Physics/v1/physicsImpostor.js";
  5. import { Observable } from "../../Misc/observable.js";
  6. import { SceneLoader } from "../../Loading/sceneLoader.js";
  7. import { Color3 } from "../../Maths/math.color.js";
  8. import { NodeMaterial } from "../../Materials/Node/nodeMaterial.js";
  9. import { Material } from "../../Materials/material.js";
  10. import { CreateIcoSphere } from "../../Meshes/Builders/icoSphereBuilder.js";
  11. import { TransformNode } from "../../Meshes/transformNode.js";
  12. import { Axis } from "../../Maths/math.axis.js";
  13. import { EngineStore } from "../../Engines/engineStore.js";
  14. /**
  15. * Parts of the hands divided to writs and finger names
  16. */
  17. export var HandPart;
  18. (function (HandPart) {
  19. /**
  20. * HandPart - Wrist
  21. */
  22. HandPart["WRIST"] = "wrist";
  23. /**
  24. * HandPart - The thumb
  25. */
  26. HandPart["THUMB"] = "thumb";
  27. /**
  28. * HandPart - Index finger
  29. */
  30. HandPart["INDEX"] = "index";
  31. /**
  32. * HandPart - Middle finger
  33. */
  34. HandPart["MIDDLE"] = "middle";
  35. /**
  36. * HandPart - Ring finger
  37. */
  38. HandPart["RING"] = "ring";
  39. /**
  40. * HandPart - Little finger
  41. */
  42. HandPart["LITTLE"] = "little";
  43. })(HandPart || (HandPart = {}));
  44. /**
  45. * Joints of the hand as defined by the WebXR specification.
  46. * https://immersive-web.github.io/webxr-hand-input/#skeleton-joints-section
  47. */
  48. export var WebXRHandJoint;
  49. (function (WebXRHandJoint) {
  50. /** Wrist */
  51. WebXRHandJoint["WRIST"] = "wrist";
  52. /** Thumb near wrist */
  53. WebXRHandJoint["THUMB_METACARPAL"] = "thumb-metacarpal";
  54. /** Thumb first knuckle */
  55. WebXRHandJoint["THUMB_PHALANX_PROXIMAL"] = "thumb-phalanx-proximal";
  56. /** Thumb second knuckle */
  57. WebXRHandJoint["THUMB_PHALANX_DISTAL"] = "thumb-phalanx-distal";
  58. /** Thumb tip */
  59. WebXRHandJoint["THUMB_TIP"] = "thumb-tip";
  60. /** Index finger near wrist */
  61. WebXRHandJoint["INDEX_FINGER_METACARPAL"] = "index-finger-metacarpal";
  62. /** Index finger first knuckle */
  63. WebXRHandJoint["INDEX_FINGER_PHALANX_PROXIMAL"] = "index-finger-phalanx-proximal";
  64. /** Index finger second knuckle */
  65. WebXRHandJoint["INDEX_FINGER_PHALANX_INTERMEDIATE"] = "index-finger-phalanx-intermediate";
  66. /** Index finger third knuckle */
  67. WebXRHandJoint["INDEX_FINGER_PHALANX_DISTAL"] = "index-finger-phalanx-distal";
  68. /** Index finger tip */
  69. WebXRHandJoint["INDEX_FINGER_TIP"] = "index-finger-tip";
  70. /** Middle finger near wrist */
  71. WebXRHandJoint["MIDDLE_FINGER_METACARPAL"] = "middle-finger-metacarpal";
  72. /** Middle finger first knuckle */
  73. WebXRHandJoint["MIDDLE_FINGER_PHALANX_PROXIMAL"] = "middle-finger-phalanx-proximal";
  74. /** Middle finger second knuckle */
  75. WebXRHandJoint["MIDDLE_FINGER_PHALANX_INTERMEDIATE"] = "middle-finger-phalanx-intermediate";
  76. /** Middle finger third knuckle */
  77. WebXRHandJoint["MIDDLE_FINGER_PHALANX_DISTAL"] = "middle-finger-phalanx-distal";
  78. /** Middle finger tip */
  79. WebXRHandJoint["MIDDLE_FINGER_TIP"] = "middle-finger-tip";
  80. /** Ring finger near wrist */
  81. WebXRHandJoint["RING_FINGER_METACARPAL"] = "ring-finger-metacarpal";
  82. /** Ring finger first knuckle */
  83. WebXRHandJoint["RING_FINGER_PHALANX_PROXIMAL"] = "ring-finger-phalanx-proximal";
  84. /** Ring finger second knuckle */
  85. WebXRHandJoint["RING_FINGER_PHALANX_INTERMEDIATE"] = "ring-finger-phalanx-intermediate";
  86. /** Ring finger third knuckle */
  87. WebXRHandJoint["RING_FINGER_PHALANX_DISTAL"] = "ring-finger-phalanx-distal";
  88. /** Ring finger tip */
  89. WebXRHandJoint["RING_FINGER_TIP"] = "ring-finger-tip";
  90. /** Pinky finger near wrist */
  91. WebXRHandJoint["PINKY_FINGER_METACARPAL"] = "pinky-finger-metacarpal";
  92. /** Pinky finger first knuckle */
  93. WebXRHandJoint["PINKY_FINGER_PHALANX_PROXIMAL"] = "pinky-finger-phalanx-proximal";
  94. /** Pinky finger second knuckle */
  95. WebXRHandJoint["PINKY_FINGER_PHALANX_INTERMEDIATE"] = "pinky-finger-phalanx-intermediate";
  96. /** Pinky finger third knuckle */
  97. WebXRHandJoint["PINKY_FINGER_PHALANX_DISTAL"] = "pinky-finger-phalanx-distal";
  98. /** Pinky finger tip */
  99. WebXRHandJoint["PINKY_FINGER_TIP"] = "pinky-finger-tip";
  100. })(WebXRHandJoint || (WebXRHandJoint = {}));
  101. const handJointReferenceArray = [
  102. WebXRHandJoint.WRIST,
  103. WebXRHandJoint.THUMB_METACARPAL,
  104. WebXRHandJoint.THUMB_PHALANX_PROXIMAL,
  105. WebXRHandJoint.THUMB_PHALANX_DISTAL,
  106. WebXRHandJoint.THUMB_TIP,
  107. WebXRHandJoint.INDEX_FINGER_METACARPAL,
  108. WebXRHandJoint.INDEX_FINGER_PHALANX_PROXIMAL,
  109. WebXRHandJoint.INDEX_FINGER_PHALANX_INTERMEDIATE,
  110. WebXRHandJoint.INDEX_FINGER_PHALANX_DISTAL,
  111. WebXRHandJoint.INDEX_FINGER_TIP,
  112. WebXRHandJoint.MIDDLE_FINGER_METACARPAL,
  113. WebXRHandJoint.MIDDLE_FINGER_PHALANX_PROXIMAL,
  114. WebXRHandJoint.MIDDLE_FINGER_PHALANX_INTERMEDIATE,
  115. WebXRHandJoint.MIDDLE_FINGER_PHALANX_DISTAL,
  116. WebXRHandJoint.MIDDLE_FINGER_TIP,
  117. WebXRHandJoint.RING_FINGER_METACARPAL,
  118. WebXRHandJoint.RING_FINGER_PHALANX_PROXIMAL,
  119. WebXRHandJoint.RING_FINGER_PHALANX_INTERMEDIATE,
  120. WebXRHandJoint.RING_FINGER_PHALANX_DISTAL,
  121. WebXRHandJoint.RING_FINGER_TIP,
  122. WebXRHandJoint.PINKY_FINGER_METACARPAL,
  123. WebXRHandJoint.PINKY_FINGER_PHALANX_PROXIMAL,
  124. WebXRHandJoint.PINKY_FINGER_PHALANX_INTERMEDIATE,
  125. WebXRHandJoint.PINKY_FINGER_PHALANX_DISTAL,
  126. WebXRHandJoint.PINKY_FINGER_TIP,
  127. ];
  128. const handPartsDefinition = {
  129. [HandPart.WRIST]: [WebXRHandJoint.WRIST],
  130. [HandPart.THUMB]: [WebXRHandJoint.THUMB_METACARPAL, WebXRHandJoint.THUMB_PHALANX_PROXIMAL, WebXRHandJoint.THUMB_PHALANX_DISTAL, WebXRHandJoint.THUMB_TIP],
  131. [HandPart.INDEX]: [
  132. WebXRHandJoint.INDEX_FINGER_METACARPAL,
  133. WebXRHandJoint.INDEX_FINGER_PHALANX_PROXIMAL,
  134. WebXRHandJoint.INDEX_FINGER_PHALANX_INTERMEDIATE,
  135. WebXRHandJoint.INDEX_FINGER_PHALANX_DISTAL,
  136. WebXRHandJoint.INDEX_FINGER_TIP,
  137. ],
  138. [HandPart.MIDDLE]: [
  139. WebXRHandJoint.MIDDLE_FINGER_METACARPAL,
  140. WebXRHandJoint.MIDDLE_FINGER_PHALANX_PROXIMAL,
  141. WebXRHandJoint.MIDDLE_FINGER_PHALANX_INTERMEDIATE,
  142. WebXRHandJoint.MIDDLE_FINGER_PHALANX_DISTAL,
  143. WebXRHandJoint.MIDDLE_FINGER_TIP,
  144. ],
  145. [HandPart.RING]: [
  146. WebXRHandJoint.RING_FINGER_METACARPAL,
  147. WebXRHandJoint.RING_FINGER_PHALANX_PROXIMAL,
  148. WebXRHandJoint.RING_FINGER_PHALANX_INTERMEDIATE,
  149. WebXRHandJoint.RING_FINGER_PHALANX_DISTAL,
  150. WebXRHandJoint.RING_FINGER_TIP,
  151. ],
  152. [HandPart.LITTLE]: [
  153. WebXRHandJoint.PINKY_FINGER_METACARPAL,
  154. WebXRHandJoint.PINKY_FINGER_PHALANX_PROXIMAL,
  155. WebXRHandJoint.PINKY_FINGER_PHALANX_INTERMEDIATE,
  156. WebXRHandJoint.PINKY_FINGER_PHALANX_DISTAL,
  157. WebXRHandJoint.PINKY_FINGER_TIP,
  158. ],
  159. };
  160. /**
  161. * Representing a single hand (with its corresponding native XRHand object)
  162. */
  163. export class WebXRHand {
  164. /**
  165. * Get the hand mesh.
  166. */
  167. get handMesh() {
  168. return this._handMesh;
  169. }
  170. /**
  171. * Get meshes of part of the hand.
  172. * @param part The part of hand to get.
  173. * @returns An array of meshes that correlate to the hand part requested.
  174. */
  175. getHandPartMeshes(part) {
  176. return handPartsDefinition[part].map((name) => this._jointMeshes[handJointReferenceArray.indexOf(name)]);
  177. }
  178. /**
  179. * Retrieves a mesh linked to a named joint in the hand.
  180. * @param jointName The name of the joint.
  181. * @returns An AbstractMesh whose position corresponds with the joint position.
  182. */
  183. getJointMesh(jointName) {
  184. return this._jointMeshes[handJointReferenceArray.indexOf(jointName)];
  185. }
  186. /**
  187. * Construct a new hand object
  188. * @param xrController The controller to which the hand correlates.
  189. * @param _jointMeshes The meshes to be used to track the hand joints.
  190. * @param _handMesh An optional hand mesh.
  191. * @param rigMapping An optional rig mapping for the hand mesh.
  192. * If not provided (but a hand mesh is provided),
  193. * it will be assumed that the hand mesh's bones are named
  194. * directly after the WebXR bone names.
  195. * @param _leftHandedMeshes Are the hand meshes left-handed-system meshes
  196. * @param _jointsInvisible Are the tracked joint meshes visible
  197. * @param _jointScaleFactor Scale factor for all joint meshes
  198. */
  199. constructor(
  200. /** The controller to which the hand correlates. */
  201. xrController, _jointMeshes, _handMesh,
  202. /** An optional rig mapping for the hand mesh. If not provided (but a hand mesh is provided),
  203. * it will be assumed that the hand mesh's bones are named directly after the WebXR bone names. */
  204. rigMapping, _leftHandedMeshes = false, _jointsInvisible = false, _jointScaleFactor = 1) {
  205. this.xrController = xrController;
  206. this._jointMeshes = _jointMeshes;
  207. this._handMesh = _handMesh;
  208. this.rigMapping = rigMapping;
  209. this._leftHandedMeshes = _leftHandedMeshes;
  210. this._jointsInvisible = _jointsInvisible;
  211. this._jointScaleFactor = _jointScaleFactor;
  212. /**
  213. * This observable will notify registered observers when the hand object has been set with a new mesh.
  214. * you can get the hand mesh using `webxrHand.handMesh`
  215. */
  216. this.onHandMeshSetObservable = new Observable();
  217. /**
  218. * Transform nodes that will directly receive the transforms from the WebXR matrix data.
  219. */
  220. this._jointTransforms = new Array(handJointReferenceArray.length);
  221. /**
  222. * The float array that will directly receive the transform matrix data from WebXR.
  223. */
  224. this._jointTransformMatrices = new Float32Array(handJointReferenceArray.length * 16);
  225. this._tempJointMatrix = new Matrix();
  226. /**
  227. * The float array that will directly receive the joint radii from WebXR.
  228. */
  229. this._jointRadii = new Float32Array(handJointReferenceArray.length);
  230. this._scene = _jointMeshes[0].getScene();
  231. // Initialize the joint transform quaternions and link the transforms to the bones.
  232. for (let jointIdx = 0; jointIdx < this._jointTransforms.length; jointIdx++) {
  233. const jointTransform = (this._jointTransforms[jointIdx] = new TransformNode(handJointReferenceArray[jointIdx], this._scene));
  234. jointTransform.rotationQuaternion = new Quaternion();
  235. // Set the rotation quaternion so we can use it later for tracking.
  236. _jointMeshes[jointIdx].rotationQuaternion = new Quaternion();
  237. }
  238. if (_handMesh) {
  239. // Note that this logic needs to happen after we initialize the joint tracking transform nodes.
  240. this.setHandMesh(_handMesh, rigMapping);
  241. }
  242. // hide the motion controller, if available/loaded
  243. if (this.xrController.motionController) {
  244. if (this.xrController.motionController.rootMesh) {
  245. this.xrController.motionController.rootMesh.dispose(false, true);
  246. }
  247. }
  248. this.xrController.onMotionControllerInitObservable.add((motionController) => {
  249. motionController._doNotLoadControllerMesh = true;
  250. });
  251. }
  252. /**
  253. * Sets the current hand mesh to render for the WebXRHand.
  254. * @param handMesh The rigged hand mesh that will be tracked to the user's hand.
  255. * @param rigMapping The mapping from XRHandJoint to bone names to use with the mesh.
  256. * @param _xrSessionManager The XRSessionManager used to initialize the hand mesh.
  257. */
  258. setHandMesh(handMesh, rigMapping, _xrSessionManager) {
  259. this._handMesh = handMesh;
  260. // Avoid any strange frustum culling. We will manually control visibility via attach and detach.
  261. handMesh.alwaysSelectAsActiveMesh = true;
  262. handMesh.getChildMeshes().forEach((mesh) => {
  263. mesh.alwaysSelectAsActiveMesh = true;
  264. });
  265. // Link the bones in the hand mesh to the transform nodes that will be bound to the WebXR tracked joints.
  266. if (this._handMesh.skeleton) {
  267. const handMeshSkeleton = this._handMesh.skeleton;
  268. handJointReferenceArray.forEach((jointName, jointIdx) => {
  269. const jointBoneIdx = handMeshSkeleton.getBoneIndexByName(rigMapping ? rigMapping[jointName] : jointName);
  270. if (jointBoneIdx !== -1) {
  271. handMeshSkeleton.bones[jointBoneIdx].linkTransformNode(this._jointTransforms[jointIdx]);
  272. }
  273. });
  274. }
  275. this.onHandMeshSetObservable.notifyObservers(this);
  276. }
  277. /**
  278. * Update this hand from the latest xr frame.
  279. * @param xrFrame The latest frame received from WebXR.
  280. * @param referenceSpace The current viewer reference space.
  281. */
  282. updateFromXRFrame(xrFrame, referenceSpace) {
  283. const hand = this.xrController.inputSource.hand;
  284. if (!hand) {
  285. return;
  286. }
  287. // TODO: Modify webxr.d.ts to better match WebXR IDL so we don't need this any cast.
  288. const anyHand = hand;
  289. const jointSpaces = handJointReferenceArray.map((jointName) => anyHand[jointName] || hand.get(jointName));
  290. let trackingSuccessful = false;
  291. if (xrFrame.fillPoses && xrFrame.fillJointRadii) {
  292. trackingSuccessful = xrFrame.fillPoses(jointSpaces, referenceSpace, this._jointTransformMatrices) && xrFrame.fillJointRadii(jointSpaces, this._jointRadii);
  293. }
  294. else if (xrFrame.getJointPose) {
  295. trackingSuccessful = true;
  296. // Warning: This codepath is slow by comparison, only here for compat.
  297. for (let jointIdx = 0; jointIdx < jointSpaces.length; jointIdx++) {
  298. const jointPose = xrFrame.getJointPose(jointSpaces[jointIdx], referenceSpace);
  299. if (jointPose) {
  300. this._jointTransformMatrices.set(jointPose.transform.matrix, jointIdx * 16);
  301. this._jointRadii[jointIdx] = jointPose.radius || 0.008;
  302. }
  303. else {
  304. trackingSuccessful = false;
  305. break;
  306. }
  307. }
  308. }
  309. if (!trackingSuccessful) {
  310. return;
  311. }
  312. handJointReferenceArray.forEach((_jointName, jointIdx) => {
  313. const jointTransform = this._jointTransforms[jointIdx];
  314. Matrix.FromArrayToRef(this._jointTransformMatrices, jointIdx * 16, this._tempJointMatrix);
  315. this._tempJointMatrix.decompose(undefined, jointTransform.rotationQuaternion, jointTransform.position);
  316. // The radius we need to make the joint in order for it to roughly cover the joints of the user's real hand.
  317. const scaledJointRadius = this._jointRadii[jointIdx] * this._jointScaleFactor;
  318. const jointMesh = this._jointMeshes[jointIdx];
  319. jointMesh.isVisible = !this._handMesh && !this._jointsInvisible;
  320. jointMesh.position.copyFrom(jointTransform.position);
  321. jointMesh.rotationQuaternion.copyFrom(jointTransform.rotationQuaternion);
  322. jointMesh.scaling.setAll(scaledJointRadius);
  323. // The WebXR data comes as right-handed, so we might need to do some conversions.
  324. if (!this._scene.useRightHandedSystem) {
  325. jointMesh.position.z *= -1;
  326. jointMesh.rotationQuaternion.z *= -1;
  327. jointMesh.rotationQuaternion.w *= -1;
  328. if (this._leftHandedMeshes && this._handMesh) {
  329. jointTransform.position.z *= -1;
  330. jointTransform.rotationQuaternion.z *= -1;
  331. jointTransform.rotationQuaternion.w *= -1;
  332. }
  333. }
  334. });
  335. if (this._handMesh) {
  336. this._handMesh.isVisible = true;
  337. }
  338. }
  339. /**
  340. * Dispose this Hand object
  341. * @param disposeMeshes Should the meshes be disposed as well
  342. */
  343. dispose(disposeMeshes = false) {
  344. if (this._handMesh) {
  345. if (disposeMeshes) {
  346. this._handMesh.skeleton?.dispose();
  347. this._handMesh.dispose(false, true);
  348. }
  349. else {
  350. this._handMesh.isVisible = false;
  351. }
  352. }
  353. }
  354. }
  355. /**
  356. * WebXR Hand Joint tracking feature, available for selected browsers and devices
  357. */
  358. export class WebXRHandTracking extends WebXRAbstractFeature {
  359. static _GenerateTrackedJointMeshes(featureOptions) {
  360. const meshes = {};
  361. ["left", "right"].map((handedness) => {
  362. const trackedMeshes = [];
  363. const originalMesh = featureOptions.jointMeshes?.sourceMesh || CreateIcoSphere("jointParent", WebXRHandTracking._ICOSPHERE_PARAMS);
  364. originalMesh.isVisible = !!featureOptions.jointMeshes?.keepOriginalVisible;
  365. for (let i = 0; i < handJointReferenceArray.length; ++i) {
  366. let newInstance = originalMesh.createInstance(`${handedness}-handJoint-${i}`);
  367. if (featureOptions.jointMeshes?.onHandJointMeshGenerated) {
  368. const returnedMesh = featureOptions.jointMeshes.onHandJointMeshGenerated(newInstance, i, handedness);
  369. if (returnedMesh) {
  370. if (returnedMesh !== newInstance) {
  371. newInstance.dispose();
  372. newInstance = returnedMesh;
  373. }
  374. }
  375. }
  376. newInstance.isPickable = false;
  377. if (featureOptions.jointMeshes?.enablePhysics) {
  378. const props = featureOptions.jointMeshes?.physicsProps || {};
  379. // downscale the instances so that physics will be initialized correctly
  380. newInstance.scaling.setAll(0.02);
  381. const type = props.impostorType !== undefined ? props.impostorType : PhysicsImpostor.SphereImpostor;
  382. newInstance.physicsImpostor = new PhysicsImpostor(newInstance, type, { mass: 0, ...props });
  383. }
  384. newInstance.rotationQuaternion = new Quaternion();
  385. newInstance.isVisible = false;
  386. trackedMeshes.push(newInstance);
  387. }
  388. meshes[handedness] = trackedMeshes;
  389. });
  390. return { left: meshes.left, right: meshes.right };
  391. }
  392. static _GenerateDefaultHandMeshesAsync(scene, xrSessionManager, options) {
  393. // eslint-disable-next-line no-async-promise-executor
  394. return new Promise(async (resolve) => {
  395. const riggedMeshes = {};
  396. // check the cache, defensive
  397. if (WebXRHandTracking._RightHandGLB?.meshes[1]?.isDisposed()) {
  398. WebXRHandTracking._RightHandGLB = null;
  399. }
  400. if (WebXRHandTracking._LeftHandGLB?.meshes[1]?.isDisposed()) {
  401. WebXRHandTracking._LeftHandGLB = null;
  402. }
  403. const handsDefined = !!(WebXRHandTracking._RightHandGLB && WebXRHandTracking._LeftHandGLB);
  404. // load them in parallel
  405. const handGLBs = await Promise.all([
  406. WebXRHandTracking._RightHandGLB ||
  407. SceneLoader.ImportMeshAsync("", WebXRHandTracking.DEFAULT_HAND_MODEL_BASE_URL, WebXRHandTracking.DEFAULT_HAND_MODEL_RIGHT_FILENAME, scene),
  408. WebXRHandTracking._LeftHandGLB ||
  409. SceneLoader.ImportMeshAsync("", WebXRHandTracking.DEFAULT_HAND_MODEL_BASE_URL, WebXRHandTracking.DEFAULT_HAND_MODEL_LEFT_FILENAME, scene),
  410. ]);
  411. WebXRHandTracking._RightHandGLB = handGLBs[0];
  412. WebXRHandTracking._LeftHandGLB = handGLBs[1];
  413. const handShader = await NodeMaterial.ParseFromFileAsync("handShader", WebXRHandTracking.DEFAULT_HAND_MODEL_SHADER_URL, scene);
  414. // depth prepass and alpha mode
  415. handShader.needDepthPrePass = true;
  416. handShader.transparencyMode = Material.MATERIAL_ALPHABLEND;
  417. handShader.alphaMode = 2;
  418. // build node materials
  419. handShader.build(false);
  420. // shader
  421. const handColors = {
  422. base: Color3.FromInts(116, 63, 203),
  423. fresnel: Color3.FromInts(149, 102, 229),
  424. fingerColor: Color3.FromInts(177, 130, 255),
  425. tipFresnel: Color3.FromInts(220, 200, 255),
  426. ...options?.handMeshes?.customColors,
  427. };
  428. const handNodes = {
  429. base: handShader.getBlockByName("baseColor"),
  430. fresnel: handShader.getBlockByName("fresnelColor"),
  431. fingerColor: handShader.getBlockByName("fingerColor"),
  432. tipFresnel: handShader.getBlockByName("tipFresnelColor"),
  433. };
  434. handNodes.base.value = handColors.base;
  435. handNodes.fresnel.value = handColors.fresnel;
  436. handNodes.fingerColor.value = handColors.fingerColor;
  437. handNodes.tipFresnel.value = handColors.tipFresnel;
  438. const isMultiview = xrSessionManager._getBaseLayerWrapper()?.isMultiview;
  439. ["left", "right"].forEach((handedness) => {
  440. const handGLB = handedness == "left" ? WebXRHandTracking._LeftHandGLB : WebXRHandTracking._RightHandGLB;
  441. if (!handGLB) {
  442. // this should never happen!
  443. throw new Error("Could not load hand model");
  444. }
  445. const handMesh = handGLB.meshes[1];
  446. handMesh._internalAbstractMeshDataInfo._computeBonesUsingShaders = true;
  447. // if in multiview do not use the material
  448. if (!isMultiview) {
  449. handMesh.material = handShader.clone(`${handedness}HandShaderClone`, true);
  450. }
  451. handMesh.isVisible = false;
  452. riggedMeshes[handedness] = handMesh;
  453. // single change for left handed systems
  454. if (!handsDefined && !scene.useRightHandedSystem) {
  455. handGLB.meshes[1].rotate(Axis.Y, Math.PI);
  456. }
  457. });
  458. handShader.dispose();
  459. resolve({ left: riggedMeshes.left, right: riggedMeshes.right });
  460. });
  461. }
  462. /**
  463. * Generates a mapping from XRHandJoint to bone name for the default hand mesh.
  464. * @param handedness The handedness being mapped for.
  465. * @returns A mapping from XRHandJoint to bone name.
  466. */
  467. static _GenerateDefaultHandMeshRigMapping(handedness) {
  468. const H = handedness == "right" ? "R" : "L";
  469. return {
  470. [WebXRHandJoint.WRIST]: `wrist_${H}`,
  471. [WebXRHandJoint.THUMB_METACARPAL]: `thumb_metacarpal_${H}`,
  472. [WebXRHandJoint.THUMB_PHALANX_PROXIMAL]: `thumb_proxPhalanx_${H}`,
  473. [WebXRHandJoint.THUMB_PHALANX_DISTAL]: `thumb_distPhalanx_${H}`,
  474. [WebXRHandJoint.THUMB_TIP]: `thumb_tip_${H}`,
  475. [WebXRHandJoint.INDEX_FINGER_METACARPAL]: `index_metacarpal_${H}`,
  476. [WebXRHandJoint.INDEX_FINGER_PHALANX_PROXIMAL]: `index_proxPhalanx_${H}`,
  477. [WebXRHandJoint.INDEX_FINGER_PHALANX_INTERMEDIATE]: `index_intPhalanx_${H}`,
  478. [WebXRHandJoint.INDEX_FINGER_PHALANX_DISTAL]: `index_distPhalanx_${H}`,
  479. [WebXRHandJoint.INDEX_FINGER_TIP]: `index_tip_${H}`,
  480. [WebXRHandJoint.MIDDLE_FINGER_METACARPAL]: `middle_metacarpal_${H}`,
  481. [WebXRHandJoint.MIDDLE_FINGER_PHALANX_PROXIMAL]: `middle_proxPhalanx_${H}`,
  482. [WebXRHandJoint.MIDDLE_FINGER_PHALANX_INTERMEDIATE]: `middle_intPhalanx_${H}`,
  483. [WebXRHandJoint.MIDDLE_FINGER_PHALANX_DISTAL]: `middle_distPhalanx_${H}`,
  484. [WebXRHandJoint.MIDDLE_FINGER_TIP]: `middle_tip_${H}`,
  485. [WebXRHandJoint.RING_FINGER_METACARPAL]: `ring_metacarpal_${H}`,
  486. [WebXRHandJoint.RING_FINGER_PHALANX_PROXIMAL]: `ring_proxPhalanx_${H}`,
  487. [WebXRHandJoint.RING_FINGER_PHALANX_INTERMEDIATE]: `ring_intPhalanx_${H}`,
  488. [WebXRHandJoint.RING_FINGER_PHALANX_DISTAL]: `ring_distPhalanx_${H}`,
  489. [WebXRHandJoint.RING_FINGER_TIP]: `ring_tip_${H}`,
  490. [WebXRHandJoint.PINKY_FINGER_METACARPAL]: `little_metacarpal_${H}`,
  491. [WebXRHandJoint.PINKY_FINGER_PHALANX_PROXIMAL]: `little_proxPhalanx_${H}`,
  492. [WebXRHandJoint.PINKY_FINGER_PHALANX_INTERMEDIATE]: `little_intPhalanx_${H}`,
  493. [WebXRHandJoint.PINKY_FINGER_PHALANX_DISTAL]: `little_distPhalanx_${H}`,
  494. [WebXRHandJoint.PINKY_FINGER_TIP]: `little_tip_${H}`,
  495. };
  496. }
  497. /**
  498. * Check if the needed objects are defined.
  499. * This does not mean that the feature is enabled, but that the objects needed are well defined.
  500. * @returns true if the needed objects for this feature are defined
  501. */
  502. isCompatible() {
  503. return typeof XRHand !== "undefined";
  504. }
  505. /**
  506. * Get the hand object according to the controller id
  507. * @param controllerId the controller id to which we want to get the hand
  508. * @returns null if not found or the WebXRHand object if found
  509. */
  510. getHandByControllerId(controllerId) {
  511. return this._attachedHands[controllerId];
  512. }
  513. /**
  514. * Get a hand object according to the requested handedness
  515. * @param handedness the handedness to request
  516. * @returns null if not found or the WebXRHand object if found
  517. */
  518. getHandByHandedness(handedness) {
  519. if (handedness == "none") {
  520. return null;
  521. }
  522. return this._trackingHands[handedness];
  523. }
  524. /**
  525. * Creates a new instance of the XR hand tracking feature.
  526. * @param _xrSessionManager An instance of WebXRSessionManager.
  527. * @param options Options to use when constructing this feature.
  528. */
  529. constructor(_xrSessionManager,
  530. /** Options to use when constructing this feature. */
  531. options) {
  532. super(_xrSessionManager);
  533. this.options = options;
  534. this._attachedHands = {};
  535. this._trackingHands = { left: null, right: null };
  536. this._handResources = { jointMeshes: null, handMeshes: null, rigMappings: null };
  537. this._worldScaleObserver = null;
  538. /**
  539. * This observable will notify registered observers when a new hand object was added and initialized
  540. */
  541. this.onHandAddedObservable = new Observable();
  542. /**
  543. * This observable will notify its observers right before the hand object is disposed
  544. */
  545. this.onHandRemovedObservable = new Observable();
  546. this._attachHand = (xrController) => {
  547. if (!xrController.inputSource.hand || xrController.inputSource.handedness == "none" || !this._handResources.jointMeshes) {
  548. return;
  549. }
  550. const handedness = xrController.inputSource.handedness;
  551. const webxrHand = new WebXRHand(xrController, this._handResources.jointMeshes[handedness], this._handResources.handMeshes && this._handResources.handMeshes[handedness], this._handResources.rigMappings && this._handResources.rigMappings[handedness], this.options.handMeshes?.meshesUseLeftHandedCoordinates, this.options.jointMeshes?.invisible, this.options.jointMeshes?.scaleFactor);
  552. this._attachedHands[xrController.uniqueId] = webxrHand;
  553. this._trackingHands[handedness] = webxrHand;
  554. this.onHandAddedObservable.notifyObservers(webxrHand);
  555. };
  556. this._detachHand = (xrController) => {
  557. this._detachHandById(xrController.uniqueId);
  558. };
  559. this.xrNativeFeatureName = "hand-tracking";
  560. // Support legacy versions of the options object by copying over joint mesh properties
  561. const anyOptions = options;
  562. const anyJointMeshOptions = anyOptions.jointMeshes;
  563. if (anyJointMeshOptions) {
  564. if (typeof anyJointMeshOptions.disableDefaultHandMesh !== "undefined") {
  565. options.handMeshes = options.handMeshes || {};
  566. options.handMeshes.disableDefaultMeshes = anyJointMeshOptions.disableDefaultHandMesh;
  567. }
  568. if (typeof anyJointMeshOptions.handMeshes !== "undefined") {
  569. options.handMeshes = options.handMeshes || {};
  570. options.handMeshes.customMeshes = anyJointMeshOptions.handMeshes;
  571. }
  572. if (typeof anyJointMeshOptions.leftHandedSystemMeshes !== "undefined") {
  573. options.handMeshes = options.handMeshes || {};
  574. options.handMeshes.meshesUseLeftHandedCoordinates = anyJointMeshOptions.leftHandedSystemMeshes;
  575. }
  576. if (typeof anyJointMeshOptions.rigMapping !== "undefined") {
  577. options.handMeshes = options.handMeshes || {};
  578. const leftRigMapping = {};
  579. const rightRigMapping = {};
  580. [
  581. [anyJointMeshOptions.rigMapping.left, leftRigMapping],
  582. [anyJointMeshOptions.rigMapping.right, rightRigMapping],
  583. ].forEach((rigMappingTuple) => {
  584. const legacyRigMapping = rigMappingTuple[0];
  585. const rigMapping = rigMappingTuple[1];
  586. legacyRigMapping.forEach((modelJointName, index) => {
  587. rigMapping[handJointReferenceArray[index]] = modelJointName;
  588. });
  589. });
  590. options.handMeshes.customRigMappings = {
  591. left: leftRigMapping,
  592. right: rightRigMapping,
  593. };
  594. }
  595. }
  596. }
  597. /**
  598. * Attach this feature.
  599. * Will usually be called by the features manager.
  600. *
  601. * @returns true if successful.
  602. */
  603. attach() {
  604. if (!super.attach()) {
  605. return false;
  606. }
  607. this._handResources = {
  608. jointMeshes: WebXRHandTracking._GenerateTrackedJointMeshes(this.options),
  609. handMeshes: this.options.handMeshes?.customMeshes || null,
  610. rigMappings: this.options.handMeshes?.customRigMappings || null,
  611. };
  612. // If they didn't supply custom meshes and are not disabling the default meshes...
  613. if (!this.options.handMeshes?.customMeshes && !this.options.handMeshes?.disableDefaultMeshes) {
  614. WebXRHandTracking._GenerateDefaultHandMeshesAsync(EngineStore.LastCreatedScene, this._xrSessionManager, this.options).then((defaultHandMeshes) => {
  615. this._handResources.handMeshes = defaultHandMeshes;
  616. this._handResources.rigMappings = {
  617. left: WebXRHandTracking._GenerateDefaultHandMeshRigMapping("left"),
  618. right: WebXRHandTracking._GenerateDefaultHandMeshRigMapping("right"),
  619. };
  620. // Apply meshes to existing hands if already tracking.
  621. this._trackingHands.left?.setHandMesh(this._handResources.handMeshes.left, this._handResources.rigMappings.left, this._xrSessionManager);
  622. this._trackingHands.right?.setHandMesh(this._handResources.handMeshes.right, this._handResources.rigMappings.right, this._xrSessionManager);
  623. this._handResources.handMeshes.left.scaling.setAll(this._xrSessionManager.worldScalingFactor);
  624. this._handResources.handMeshes.right.scaling.setAll(this._xrSessionManager.worldScalingFactor);
  625. });
  626. this._worldScaleObserver = this._xrSessionManager.onWorldScaleFactorChangedObservable.add((scalingFactors) => {
  627. if (this._handResources.handMeshes) {
  628. this._handResources.handMeshes.left.scaling.scaleInPlace(scalingFactors.newScaleFactor / scalingFactors.previousScaleFactor);
  629. this._handResources.handMeshes.right.scaling.scaleInPlace(scalingFactors.newScaleFactor / scalingFactors.previousScaleFactor);
  630. }
  631. });
  632. }
  633. this.options.xrInput.controllers.forEach(this._attachHand);
  634. this._addNewAttachObserver(this.options.xrInput.onControllerAddedObservable, this._attachHand);
  635. this._addNewAttachObserver(this.options.xrInput.onControllerRemovedObservable, this._detachHand);
  636. return true;
  637. }
  638. _onXRFrame(_xrFrame) {
  639. this._trackingHands.left?.updateFromXRFrame(_xrFrame, this._xrSessionManager.referenceSpace);
  640. this._trackingHands.right?.updateFromXRFrame(_xrFrame, this._xrSessionManager.referenceSpace);
  641. }
  642. _detachHandById(controllerId, disposeMesh) {
  643. const hand = this.getHandByControllerId(controllerId);
  644. if (hand) {
  645. const handedness = hand.xrController.inputSource.handedness == "left" ? "left" : "right";
  646. if (this._trackingHands[handedness]?.xrController.uniqueId === controllerId) {
  647. this._trackingHands[handedness] = null;
  648. }
  649. this.onHandRemovedObservable.notifyObservers(hand);
  650. hand.dispose(disposeMesh);
  651. delete this._attachedHands[controllerId];
  652. }
  653. }
  654. /**
  655. * Detach this feature.
  656. * Will usually be called by the features manager.
  657. *
  658. * @returns true if successful.
  659. */
  660. detach() {
  661. if (!super.detach()) {
  662. return false;
  663. }
  664. Object.keys(this._attachedHands).forEach((uniqueId) => this._detachHandById(uniqueId, this.options.handMeshes?.disposeOnSessionEnd));
  665. if (this.options.handMeshes?.disposeOnSessionEnd) {
  666. if (this._handResources.jointMeshes) {
  667. this._handResources.jointMeshes.left.forEach((trackedMesh) => trackedMesh.dispose());
  668. this._handResources.jointMeshes.right.forEach((trackedMesh) => trackedMesh.dispose());
  669. }
  670. }
  671. // remove world scale observer
  672. if (this._worldScaleObserver) {
  673. this._xrSessionManager.onWorldScaleFactorChangedObservable.remove(this._worldScaleObserver);
  674. }
  675. return true;
  676. }
  677. /**
  678. * Dispose this feature and all of the resources attached.
  679. */
  680. dispose() {
  681. super.dispose();
  682. this.onHandAddedObservable.clear();
  683. this.onHandRemovedObservable.clear();
  684. if (this._handResources.handMeshes && !this.options.handMeshes?.customMeshes) {
  685. // this will dispose the cached meshes
  686. this._handResources.handMeshes.left.dispose();
  687. this._handResources.handMeshes.right.dispose();
  688. // remove the cached meshes
  689. WebXRHandTracking._RightHandGLB = null;
  690. WebXRHandTracking._LeftHandGLB = null;
  691. }
  692. if (this._handResources.jointMeshes) {
  693. this._handResources.jointMeshes.left.forEach((trackedMesh) => trackedMesh.dispose());
  694. this._handResources.jointMeshes.right.forEach((trackedMesh) => trackedMesh.dispose());
  695. }
  696. }
  697. }
  698. /**
  699. * The module's name
  700. */
  701. WebXRHandTracking.Name = WebXRFeatureName.HAND_TRACKING;
  702. /**
  703. * The (Babylon) version of this module.
  704. * This is an integer representing the implementation version.
  705. * This number does not correspond to the WebXR specs version
  706. */
  707. WebXRHandTracking.Version = 1;
  708. /** The base URL for the default hand model. */
  709. WebXRHandTracking.DEFAULT_HAND_MODEL_BASE_URL = "https://assets.babylonjs.com/meshes/HandMeshes/";
  710. /** The filename to use for the default right hand model. */
  711. WebXRHandTracking.DEFAULT_HAND_MODEL_RIGHT_FILENAME = "r_hand_rhs.glb";
  712. /** The filename to use for the default left hand model. */
  713. WebXRHandTracking.DEFAULT_HAND_MODEL_LEFT_FILENAME = "l_hand_rhs.glb";
  714. /** The URL pointing to the default hand model NodeMaterial shader. */
  715. WebXRHandTracking.DEFAULT_HAND_MODEL_SHADER_URL = "https://assets.babylonjs.com/meshes/HandMeshes/handsShader.json";
  716. // We want to use lightweight models, diameter will initially be 1 but scaled to the values returned from WebXR.
  717. WebXRHandTracking._ICOSPHERE_PARAMS = { radius: 0.5, flat: false, subdivisions: 2 };
  718. WebXRHandTracking._RightHandGLB = null;
  719. WebXRHandTracking._LeftHandGLB = null;
  720. //register the plugin
  721. WebXRFeaturesManager.AddWebXRFeature(WebXRHandTracking.Name, (xrSessionManager, options) => {
  722. return () => new WebXRHandTracking(xrSessionManager, options);
  723. }, WebXRHandTracking.Version, false);
  724. //# sourceMappingURL=WebXRHandTracking.js.map