main.lua 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. --[[ Hand interaction with physics world: use trigger to solidify hand, grip to grab objects
  2. To manipulate objects in world, we create box collider (palm) for each hand controller. This box
  3. is updated to track location of controller.
  4. The naive approach would be to set exact location and orientation of physical collider with values
  5. from hand controller. This results in lousy and unconvincing collisions with other objects, as
  6. physics engine doesn't know the speed of hand colliders at the moment of collision.
  7. An improvement is to set linear and angular speed of kinematic hand colliders so that they
  8. approach the target (actual location/orientation of hand controller). This works well for one
  9. hand, however physics will start to glitch when you try to squeeze an object between two hands.
  10. This is because kinematic hand controllers can never be affected by collision forces, so the
  11. squeezed collider cannot push back against them and the collision cannot be resolved.
  12. The approach taken here is to have hand controllers behave as normal dynamic colliders that can be
  13. affected by other collisions. To track hand controllers, we apply force and torque on collider
  14. objects that's proportional to distance from correct position.
  15. This means hand colliders won't have 1:1 mapping with actual hand controllers, they will actually
  16. 'bend' under large force. Also the colliders can become stuck and buried beneath other objects.
  17. This is frustrating to users, so in this example hand colliders can ghost through objects or
  18. become solid, using the trigger button.
  19. Grabbing objects is done by creating two joints between hand collider and object, to hold them
  20. together. This enables pulling, stacking and throwing. --]]
  21. local hands = { -- palms that can push and grab objects
  22. colliders = {nil, nil}, -- physical objects for palms
  23. touching = {nil, nil}, -- the collider currently touched by each hand
  24. holding = {nil, nil}, -- the collider attached to palm
  25. solid = {false, false}, -- hand can either pass through objects or be solid
  26. } -- to be filled with as many hands as there are active controllers
  27. local world
  28. local collisionCallbacks = {}
  29. local boxes = {}
  30. local framerate = 1 / 72 -- fixed framerate is recommended for physics updates
  31. local hand_torque = 20
  32. local hand_force = 30000
  33. function lovr.load()
  34. world = lovr.physics.newWorld(0, -2, 0, false) -- low gravity and no collider sleeping
  35. -- ground plane
  36. local box = world:newBoxCollider(vec3(0, 0, 0), vec3(20, 0.1, 20))
  37. box:setKinematic(true)
  38. table.insert(boxes, box)
  39. -- create a fort of boxes
  40. lovr.math.setRandomSeed(0)
  41. for angle = 0, 2 * math.pi, 2 * math.pi / 12 do
  42. for height = 0.3, 1.5, 0.4 do
  43. local pose = mat4():rotate(angle, 0,1,0):translate(0, height, -1)
  44. local size = vec3(0.3, 0.4, 0.2)
  45. local box = world:newBoxCollider(vec3(pose), size)
  46. box:setOrientation(quat(pose))
  47. table.insert(boxes, box)
  48. end
  49. end
  50. -- make colliders for two hands
  51. for i = 1, 2 do
  52. hands.colliders[i] = world:newBoxCollider(vec3(0,2,0), vec3(0.04, 0.08, 0.08))
  53. hands.colliders[i]:setLinearDamping(0.7)
  54. hands.colliders[i]:setAngularDamping(0.9)
  55. hands.colliders[i]:setMass(0.5)
  56. registerCollisionCallback(hands.colliders[i],
  57. function(collider, world)
  58. -- store collider that was last touched by hand
  59. hands.touching[i] = collider
  60. end)
  61. end
  62. end
  63. function lovr.update(dt)
  64. -- override collision resolver to notify all colliders that have registered their callbacks
  65. world:update(framerate, function(world)
  66. world:computeOverlaps()
  67. for shapeA, shapeB in world:overlaps() do
  68. local areColliding = world:collide(shapeA, shapeB)
  69. if areColliding then
  70. cbA = collisionCallbacks[shapeA]
  71. if cbA then cbA(shapeB:getCollider(), world) end
  72. cbB = collisionCallbacks[shapeB]
  73. if cbB then cbB(shapeA:getCollider(), world) end
  74. end
  75. end
  76. end)
  77. -- hand updates - location, orientation, solidify on trigger button, grab on grip button
  78. for i, hand in pairs(lovr.headset.getHands()) do
  79. -- align collider with controller by applying force (position) and torque (orientation)
  80. local rw = mat4(lovr.headset.getPose(hand)) -- real world pose of controllers
  81. local vr = mat4(hands.colliders[i]:getPose()) -- vr pose of palm colliders
  82. local angle, ax,ay,az = quat(rw):mul(quat(vr):conjugate()):unpack()
  83. angle = ((angle + math.pi) % (2 * math.pi) - math.pi) -- for minimal motion wrap to (-pi, +pi) range
  84. hands.colliders[i]:applyTorque(vec3(ax, ay, az):mul(angle * dt * hand_torque))
  85. hands.colliders[i]:applyForce((vec3(rw) - vec3(vr)):mul(dt * hand_force))
  86. -- solidify when trigger touched
  87. hands.solid[i] = lovr.headset.isDown(hand, 'trigger')
  88. hands.colliders[i]:getShapes()[1]:setSensor(not hands.solid[i])
  89. -- hold/release colliders
  90. if lovr.headset.isDown(hand, 'grip') and hands.touching[i] and not hands.holding[i] then
  91. hands.holding[i] = hands.touching[i]
  92. -- grab object with ball joint to drag it, and slider joint to also match the orientation
  93. lovr.physics.newBallJoint(hands.colliders[i], hands.holding[i], vr:mul(0, 0, 0))
  94. lovr.physics.newSliderJoint(hands.colliders[i], hands.holding[i], quat(vr):direction())
  95. end
  96. if lovr.headset.wasReleased(hand, 'grip') and hands.holding[i] then
  97. for _,joint in ipairs(hands.colliders[i]:getJoints()) do
  98. joint:destroy()
  99. end
  100. hands.holding[i] = nil
  101. end
  102. end
  103. hands.touching = {nil, nil} -- to be set again in collision resolver
  104. end
  105. function lovr.draw(pass)
  106. for i, collider in ipairs(hands.colliders) do
  107. pass:setColor(0.75, 0.56, 0.44)
  108. drawBoxCollider(pass, collider, not hands.solid[i])
  109. end
  110. lovr.math.setRandomSeed(0)
  111. for i, collider in ipairs(boxes) do
  112. local shade = 0.2 + 0.6 * lovr.math.random()
  113. pass:setColor(shade, shade, shade)
  114. drawBoxCollider(pass, collider)
  115. end
  116. end
  117. function drawBoxCollider(pass, collider, is_sensor)
  118. -- query current pose (location and orientation)
  119. local pose = mat4(collider:getPose())
  120. -- query dimensions of box
  121. local shape = collider:getShapes()[1]
  122. local size = vec3(shape:getDimensions())
  123. -- draw box
  124. pose:scale(size)
  125. pass:box(pose, is_sensor and 'line' or 'fill')
  126. end
  127. function registerCollisionCallback(collider, callback)
  128. collisionCallbacks = collisionCallbacks or {}
  129. for _, shape in ipairs(collider:getShapes()) do
  130. collisionCallbacks[shape] = callback
  131. end
  132. -- to be called with arguments callback(otherCollider, world) from update function
  133. end