main.lua 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. -- Some notes about the models used here:
  2. -- * They are MIT licensed, originally from here:
  3. -- https://github.com/immersive-web/webxr-input-profiles/tree/main/packages/assets/profiles/generic-hand
  4. -- * This particular model has all of the bones parented to a
  5. -- single root Armature node. Other approaches will need
  6. -- to be used for models that are rigged differently, like if
  7. -- the nodes are parented to each other in a hierarchy.
  8. -- * This model was rigged for the Quest. It works on other
  9. -- hand tracking systems, but differences in metacarpal bones
  10. -- might cause the wrist vertices to get scrunched up a bit.
  11. -- Rigging Process
  12. ------------------
  13. -- The wrist joint/node is used as a shared reference point.
  14. -- There are 4 coordinate spaces:
  15. -- 1) world space (normal headset coordinate space)
  16. -- 2) grip space (relative to hand device pose)
  17. -- 3) wrist space (relative to wrist joint from getSkeleton)
  18. -- 4) model space (relative to the model root node)
  19. -- The steps to compute the local pose of a node in the model:
  20. -- * Start with the world-space joint from getSkeleton
  21. -- * Compute its pose relative to the wrist joint (wristFromWorld)
  22. -- * We need a pose relative to the root Armature, not relative
  23. -- to the wrist. To get this, apply the pose of the wrist node
  24. -- in the model (modelFromWrist).
  25. -- * So the final transform is (modelFromWrist * wristFromWorld)
  26. local function animateHand(device, skeleton, model, map)
  27. model:resetNodeTransforms()
  28. if not skeleton then return end
  29. -- Get offset of wrist node in the model
  30. local modelFromWrist = mat4(model:getNodeTransform(map[2]))
  31. local wristFromModel = mat4(modelFromWrist):invert()
  32. -- Get offset of wrist joint in the world
  33. local x, y, z, _, angle, ax, ay, az = unpack(skeleton[2])
  34. local worldFromWrist = mat4(x, y, z, angle, ax, ay, az)
  35. local wristFromWorld = mat4(worldFromWrist):invert()
  36. -- Combine the two into a matrix that will transform the
  37. -- world-space hand joints into local node poses for the model
  38. local modelFromWorld = modelFromWrist * wristFromWorld
  39. -- Transform the nodes
  40. for index, node in pairs(map) do
  41. local x, y, z, _, angle, ax, ay, az = unpack(skeleton[index])
  42. local jointWorld = mat4(x, y, z, angle, ax, ay, az)
  43. local jointModel = modelFromWorld * jointWorld
  44. model:setNodeTransform(node, jointModel)
  45. end
  46. -- This offsets the root node so the wrist poses line up when the
  47. -- model is drawn at the hand pose. Instead of doing this, you
  48. -- could just draw the model at worldFromWrist * wristFromModel
  49. local worldFromGrip = mat4(lovr.headset.getPose(device))
  50. local gripFromWorld = mat4(worldFromGrip):invert()
  51. model:setNodeTransform(model:getRootNode(), gripFromWorld * worldFromWrist * wristFromModel)
  52. end
  53. function lovr.load()
  54. hands = {}
  55. for i, hand in ipairs({ 'left', 'right' }) do
  56. hands[hand] = {
  57. model = lovr.graphics.newModel(hand .. '.glb'),
  58. skeleton = nil
  59. }
  60. end
  61. -- Maps skeleton joint index to node names in the model
  62. map = {
  63. [2] = 'wrist',
  64. [3] = 'thumb-metacarpal',
  65. [4] = 'thumb-phalanx-proximal',
  66. [5] = 'thumb-phalanx-distal',
  67. [7] = 'index-finger-metacarpal',
  68. [8] = 'index-finger-phalanx-proximal',
  69. [9] = 'index-finger-phalanx-intermediate',
  70. [10] = 'index-finger-phalanx-distal',
  71. [12] = 'middle-finger-metacarpal',
  72. [13] = 'middle-finger-phalanx-proximal',
  73. [14] = 'middle-finger-phalanx-intermediate',
  74. [15] = 'middle-finger-phalanx-distal',
  75. [17] = 'ring-finger-metacarpal',
  76. [18] = 'ring-finger-phalanx-proximal',
  77. [19] = 'ring-finger-phalanx-intermediate',
  78. [20] = 'ring-finger-phalanx-distal',
  79. [22] = 'pinky-finger-metacarpal',
  80. [23] = 'pinky-finger-phalanx-proximal',
  81. [24] = 'pinky-finger-phalanx-intermediate',
  82. [25] = 'pinky-finger-phalanx-distal'
  83. }
  84. end
  85. function lovr.update(dt)
  86. for device, hand in pairs(hands) do
  87. hand.skeleton = lovr.headset.getSkeleton(device)
  88. animateHand(device, hand.skeleton, hand.model, map)
  89. end
  90. end
  91. function lovr.draw(pass)
  92. lovr.graphics.setBackgroundColor(0x202224)
  93. if not hands.left.skeleton and not hands.right.skeleton then
  94. pass:text('No skelly :(', 0, 1, -1, .1)
  95. return
  96. end
  97. for device, hand in pairs(hands) do
  98. if hand.skeleton then
  99. -- Debug dots for joints
  100. pass:setColor(0x8000ff)
  101. pass:setDepthWrite(false)
  102. for i = 1, #hand.skeleton do
  103. local x, y, z, _, angle, ax, ay, az = unpack(hand.skeleton[i])
  104. pass:sphere(mat4(x, y, z, angle, ax, ay, az):scale(.003))
  105. end
  106. pass:setDepthWrite(true)
  107. -- Draw the (procedurally animated) wireframe hand model
  108. local worldFromGrip = mat4(lovr.headset.getPose(device))
  109. pass:setColor(0xffffff)
  110. pass:setWireframe(true)
  111. pass:draw(hand.model, worldFromGrip)
  112. pass:setWireframe(false)
  113. end
  114. end
  115. end