main.lua 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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 excellent for one
  9. controller. When you try to squeeze an object between two hands, physics break. This is because
  10. kinematic hand controllers are never affected by physics engine and unrealistic material
  11. penetration 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 actually become stuck behind another object. This
  17. is sometimes frustrating to use, so in this example hand colliders can ghost through objects or
  18. become solid using 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. function lovr.load()
  32. world = lovr.physics.newWorld(0, -2, 0, false) -- low gravity and no collider sleeping
  33. -- ground plane
  34. local box = world:newBoxCollider(vec3(0, 0, 0), vec3(20, 0.1, 20))
  35. box:setKinematic(true)
  36. table.insert(boxes, box)
  37. -- create a fort of boxes
  38. lovr.math.setRandomSeed(0)
  39. for angle = 0, 2 * math.pi, 2 * math.pi / 12 do
  40. for height = 0.3, 1.5, 0.4 do
  41. local pose = mat4():rotate(angle, 0,1,0):translate(0, height, -1)
  42. local size = vec3(0.3, 0.4, 0.2)
  43. local box = world:newBoxCollider(vec3(pose), size)
  44. box:setOrientation(quat(pose))
  45. table.insert(boxes, box)
  46. end
  47. end
  48. -- make colliders for two hands
  49. for i = 1, 2 do
  50. hands.colliders[i] = world:newBoxCollider(vec3(0,2,0), vec3(0.04, 0.08, 0.08))
  51. hands.colliders[i]:setLinearDamping(0.2)
  52. hands.colliders[i]:setAngularDamping(0.3)
  53. hands.colliders[i]:setMass(0.1)
  54. registerCollisionCallback(hands.colliders[i],
  55. function(collider, world)
  56. -- store collider that was last touched by hand
  57. hands.touching[i] = collider
  58. end)
  59. end
  60. end
  61. function lovr.update(dt)
  62. -- override collision resolver to notify all colliders that have registered their callbacks
  63. world:update(framerate, function(world)
  64. world:computeOverlaps()
  65. for shapeA, shapeB in world:overlaps() do
  66. local areColliding = world:collide(shapeA, shapeB)
  67. if areColliding then
  68. cbA = collisionCallbacks[shapeA]
  69. if cbA then cbA(shapeB:getCollider(), world) end
  70. cbB = collisionCallbacks[shapeB]
  71. if cbB then cbB(shapeA:getCollider(), world) end
  72. end
  73. end
  74. end)
  75. -- hand updates - location, orientation, solidify on trigger button, grab on grip button
  76. for i, hand in pairs(lovr.headset.getHands()) do
  77. -- align collider with controller by applying force (position) and torque (orientation)
  78. local rw = mat4(lovr.headset.getPose(hand)) -- real world pose of controllers
  79. local vr = mat4(hands.colliders[i]:getPose()) -- vr pose of palm colliders
  80. local angle, ax,ay,az = quat(rw):mul(quat(vr):conjugate()):unpack()
  81. angle = ((angle + math.pi) % (2 * math.pi) - math.pi) -- for minimal motion wrap to (-pi, +pi) range
  82. hands.colliders[i]:applyTorque(vec3(ax, ay, az):mul(angle * dt * 1))
  83. hands.colliders[i]:applyForce((vec3(rw:mul(0,0,0)) - vec3(vr:mul(0,0,0))):mul(dt * 2000))
  84. -- solidify when trigger touched
  85. hands.solid[i] = lovr.headset.isDown(hand, 'trigger')
  86. hands.colliders[i]:getShapes()[1]:setSensor(not hands.solid[i])
  87. -- hold/release colliders
  88. if lovr.headset.isDown(hand, 'grip') and hands.touching[i] and not hands.holding[i] then
  89. hands.holding[i] = hands.touching[i]
  90. lovr.physics.newBallJoint(hands.colliders[i], hands.holding[i], vr:mul(0,0,0))
  91. lovr.physics.newSliderJoint(hands.colliders[i], hands.holding[i], quat(vr):direction())
  92. end
  93. if lovr.headset.wasReleased(hand, 'grip') and hands.holding[i] then
  94. for _,joint in ipairs(hands.colliders[i]:getJoints()) do
  95. joint:destroy()
  96. end
  97. hands.holding[i] = nil
  98. end
  99. end
  100. hands.touching = {nil, nil} -- to be set again in collision resolver
  101. end
  102. function lovr.draw()
  103. for i, collider in ipairs(hands.colliders) do
  104. local alpha = hands.solid[i] and 1 or 0.2
  105. lovr.graphics.setColor(0.75, 0.56, 0.44, alpha)
  106. drawBoxCollider(collider)
  107. end
  108. lovr.math.setRandomSeed(0)
  109. for i, collider in ipairs(boxes) do
  110. local shade = 0.2 + 0.6 * lovr.math.random()
  111. lovr.graphics.setColor(shade, shade, shade)
  112. drawBoxCollider(collider)
  113. end
  114. end
  115. function drawBoxCollider(collider)
  116. -- query current pose (location and orientation)
  117. local pose = mat4(collider:getPose())
  118. -- query dimensions of box
  119. local shape = collider:getShapes()[1]
  120. local size = vec3(shape:getDimensions())
  121. -- draw box
  122. pose:scale(size)
  123. lovr.graphics.box('fill', pose)
  124. end
  125. function registerCollisionCallback(collider, callback)
  126. collisionCallbacks = collisionCallbacks or {}
  127. for _, shape in ipairs(collider:getShapes()) do
  128. collisionCallbacks[shape] = callback
  129. end
  130. -- to be called with arguments callback(otherCollider, world) from update function
  131. end