45_InverseKinematics.lua 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. -- Ribbon trail demo.
  2. -- This sample demonstrates how to adjust the position of animated feet so they match the ground's angle using IK.
  3. require "LuaScripts/Utilities/Sample"
  4. local jackAnimCtrl_
  5. local cameraRotateNode_
  6. local floorNode_
  7. local leftFoot_
  8. local rightFoot_
  9. local leftEffector_
  10. local rightEffector_
  11. local solver_
  12. local floorPitch_ = 0.0
  13. local floorRoll_ = 0.0
  14. local drawDebug_ = false
  15. function Start()
  16. cache.autoReloadResources = true
  17. -- Execute the common startup for samples
  18. SampleStart()
  19. -- Create the scene content
  20. CreateScene()
  21. -- Create the UI content
  22. CreateInstructions()
  23. -- Setup the viewport for displaying the scene
  24. SetupViewport()
  25. -- Set the mouse mode to use in the sample
  26. SampleInitMouseMode(MM_RELATIVE)
  27. -- Hook up to the frame update events
  28. SubscribeToEvents()
  29. end
  30. function CreateScene()
  31. scene_ = Scene()
  32. -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
  33. scene_:CreateComponent("Octree")
  34. scene_:CreateComponent("DebugRenderer")
  35. scene_:CreateComponent("PhysicsWorld")
  36. -- Create scene node & StaticModel component for showing a static plane
  37. floorNode_ = scene_:CreateChild("Plane")
  38. floorNode_.scale = Vector3(50.0, 1.0, 50.0)
  39. local planeObject = floorNode_:CreateComponent("StaticModel")
  40. planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
  41. planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
  42. -- Set up collision, we need to raycast to determine foot height
  43. floorNode_:CreateComponent("RigidBody")
  44. local col = floorNode_:CreateComponent("CollisionShape")
  45. col:SetBox(Vector3(1, 0, 1))
  46. -- Create a directional light to the world.
  47. local lightNode = scene_:CreateChild("DirectionalLight")
  48. lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
  49. local light = lightNode:CreateComponent("Light")
  50. light.lightType = LIGHT_DIRECTIONAL
  51. light.castShadows = true
  52. light.shadowBias = BiasParameters(0.00005, 0.5)
  53. -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
  54. light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
  55. -- Load Jack animated model
  56. jackNode_ = scene_:CreateChild("Jack")
  57. jackNode_.rotation = Quaternion(0.0, 270.0, 0.0)
  58. jack = jackNode_:CreateComponent("AnimatedModel")
  59. jack.model = cache:GetResource("Model", "Models/Jack.mdl")
  60. jack.material = cache:GetResource("Material", "Materials/Jack.xml")
  61. jack.castShadows = true
  62. -- Create animation controller and play walk animation
  63. jackAnimCtrl_ = jackNode_:CreateComponent("AnimationController")
  64. jackAnimCtrl_:PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0)
  65. -- We need to attach two inverse kinematic effectors to Jack's feet to
  66. -- control the grounding.
  67. leftFoot_ = jackNode_:GetChild("Bip01_L_Foot", true)
  68. rightFoot_ = jackNode_:GetChild("Bip01_R_Foot", true)
  69. leftEffector_ = leftFoot_:CreateComponent("IKEffector")
  70. rightEffector_ = rightFoot_:CreateComponent("IKEffector")
  71. -- Control 2 segments up to the hips
  72. leftEffector_.chainLength = 2
  73. rightEffector_.chainLength = 2
  74. -- For the effectors to work, an IKSolver needs to be attached to one of
  75. -- the parent nodes. Typically, you want to place the solver as close as
  76. -- possible to the effectors for optimal performance. Since in this case
  77. -- we're solving the legs only, we can place the solver at the spine.
  78. local spine = jackNode_:GetChild("Bip01_Spine", true)
  79. solver_ = spine:CreateComponent("IKSolver")
  80. -- Two-bone solver is more efficient and more stable than FABRIK (but only
  81. -- works for two bones, obviously).
  82. solver_.algorithm = IKSolver.TWO_BONE
  83. -- Disable auto-solving, which means we can call Solve() manually.
  84. solver_.AUTO_SOLVE = false
  85. -- Only enable this so the debug draw shows us the pose before solving.
  86. -- This should NOT be enabled for any other reason (it does nothing and is
  87. -- a waste of performance).
  88. solver_.UPDATE_ORIGINAL_POSE = true
  89. -- Create the camera.
  90. cameraRotateNode_ = scene_:CreateChild("CameraRotate")
  91. cameraNode = cameraRotateNode_:CreateChild("Camera")
  92. cameraNode:CreateComponent("Camera")
  93. -- Set an initial position for the camera scene node above the plane
  94. cameraNode.position = Vector3(0.0, 0.0, -4.0)
  95. cameraRotateNode_.position = Vector3(0.0, 0.4, 0.0)
  96. pitch = 20.0
  97. yaw = 50.0
  98. end
  99. function CreateInstructions()
  100. -- Construct new Text object, set string to display and font to use
  101. local instructionText = ui.root:CreateChild("Text")
  102. instructionText:SetText("Left-Click and drag to look around\nRight-Click and drag to change incline\nPress space to reset floor\nPress D to draw debug geometry")
  103. instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
  104. -- Position the text relative to the screen center
  105. instructionText.horizontalAlignment = HA_CENTER
  106. instructionText.verticalAlignment = VA_CENTER
  107. instructionText:SetPosition(0, ui.root.height / 4)
  108. end
  109. function SetupViewport()
  110. -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
  111. -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
  112. -- use, but now we just use full screen and default render path configured in the engine command line options
  113. local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
  114. renderer:SetViewport(0, viewport)
  115. end
  116. function UpdateCameraAndFloor(timeStep)
  117. -- Do not move if the UI has a focused element (the console)
  118. if ui.focusElement ~= nil then
  119. return
  120. end
  121. -- Mouse sensitivity as degrees per pixel
  122. local MOUSE_SENSITIVITY = 0.1
  123. -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
  124. if input:GetMouseButtonDown(MOUSEB_LEFT) then
  125. local mouseMove = input.mouseMove
  126. yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
  127. pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
  128. pitch = Clamp(pitch, -90.0, 90.0)
  129. end
  130. if input:GetMouseButtonDown(MOUSEB_RIGHT) then
  131. local mouseMoveInt = input.mouseMove
  132. local mouseMove = Vector2()
  133. mouseMove.x = -Cos(yaw) * mouseMoveInt.y - Sin(yaw) * mouseMoveInt.x
  134. mouseMove.y = Sin(yaw) * mouseMoveInt.y - Cos(yaw) * mouseMoveInt.x
  135. floorPitch_ = floorPitch_ + MOUSE_SENSITIVITY * mouseMove.x
  136. floorPitch_ = Clamp(floorPitch_, -90.0, 90.0)
  137. floorRoll_ = floorRoll_ + MOUSE_SENSITIVITY * mouseMove.y
  138. end
  139. if input:GetKeyPress(KEY_SPACE) then
  140. floorPitch_ = 0.0
  141. floorRoll_ = 0.0
  142. end
  143. if input:GetKeyPress(KEY_D) then
  144. drawDebug_ = not drawDebug_
  145. end
  146. -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
  147. cameraRotateNode_.rotation = Quaternion(pitch, yaw, 0.0)
  148. floorNode_.rotation = Quaternion(floorPitch_, 0.0, floorRoll_)
  149. end
  150. function SubscribeToEvents()
  151. -- Subscribe HandleUpdate() function for processing update events
  152. SubscribeToEvent("Update", "HandleUpdate")
  153. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
  154. SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
  155. end
  156. function HandleUpdate(eventType, eventData)
  157. -- Take the frame time step, which is stored as a float
  158. local timeStep = eventData["TimeStep"]:GetFloat()
  159. -- Move the camera, scale movement with time step
  160. UpdateCameraAndFloor(timeStep)
  161. end
  162. function HandlePostRenderUpdate(eventType, eventData)
  163. if drawDebug_ then
  164. solver_:DrawDebugGeometry(false)
  165. end
  166. end
  167. function HandleSceneDrawableUpdateFinished(eventType, eventData)
  168. local physicsWorld = scene_:GetComponent("PhysicsWorld")
  169. local leftFootPosition = leftFoot_.worldPosition
  170. local rightFootPosition = rightFoot_.worldPosition
  171. -- Cast ray down to get the normal of the underlying surface
  172. local result = physicsWorld:RaycastSingle(Ray(leftFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
  173. if result.body then
  174. -- Cast again, but this time along the normal. Set the target position
  175. -- to the ray intersection
  176. local oppositeNormal = result.normal * -1
  177. result = physicsWorld:RaycastSingle(Ray(leftFootPosition + result.normal, oppositeNormal), 2)
  178. -- The foot node has an offset relative to the root node
  179. footOffset = leftFoot_.worldPosition.y - jackNode_.worldPosition.y
  180. leftEffector_.targetPosition = result.position + result.normal * footOffset
  181. -- Rotate foot according to normal
  182. leftFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
  183. end
  184. -- Same deal with the right foot
  185. result = physicsWorld:RaycastSingle(Ray(rightFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
  186. if result.body then
  187. local oppositeNormal = result.normal * -1
  188. result = physicsWorld:RaycastSingle(Ray(rightFootPosition + result.normal, oppositeNormal), 2)
  189. footOffset = rightFoot_.worldPosition.y - jackNode_.worldPosition.y
  190. rightEffector_.targetPosition = result.position + result.normal * footOffset
  191. rightFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
  192. end
  193. solver_:Solve()
  194. end