45_InverseKinematics.as 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Inverse Kinematics
  2. // This sample demonstrates how to use the IK solver to create "grounders" for a walking character on a slope.
  3. #include "Scripts/Utilities/Sample.as"
  4. AnimationController@ jackAnimCtrl_;
  5. Node@ cameraRotateNode_;
  6. Node@ floorNode_;
  7. Node@ leftFoot_;
  8. Node@ rightFoot_;
  9. Node@ jackNode_;
  10. IKEffector@ leftEffector_;
  11. IKEffector@ rightEffector_;
  12. IKSolver@ solver_;
  13. float floorPitch_ = 0.0f;
  14. float floorRoll_ = 0.0f;
  15. bool drawDebug_ = false;
  16. void Start()
  17. {
  18. cache.autoReloadResources = true;
  19. // Execute the common startup for samples
  20. SampleStart();
  21. // Create the scene content
  22. CreateScene();
  23. // Create the UI content
  24. CreateInstructions();
  25. // Setup the viewport for displaying the scene
  26. SetupViewport();
  27. // Set the mouse mode to use in the sample
  28. SampleInitMouseMode(MM_RELATIVE);
  29. // Hook up to the frame update events
  30. SubscribeToEvents();
  31. }
  32. void CreateScene()
  33. {
  34. scene_ = Scene();
  35. // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
  36. scene_.CreateComponent("Octree");
  37. scene_.CreateComponent("DebugRenderer");
  38. scene_.CreateComponent("PhysicsWorld");
  39. // Create scene node & StaticModel component for showing a static plane
  40. floorNode_ = scene_.CreateChild("Plane");
  41. floorNode_.scale = Vector3(50.0f, 1.0f, 50.0f);
  42. StaticModel@ planeObject = floorNode_.CreateComponent("StaticModel");
  43. planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
  44. planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
  45. // Set up collision, we need to raycast to determine foot height
  46. floorNode_.CreateComponent("RigidBody");
  47. CollisionShape@ col = floorNode_.CreateComponent("CollisionShape");
  48. col.SetBox(Vector3(1, 0, 1));
  49. // Create a directional light to the world.
  50. Node@ lightNode = scene_.CreateChild("DirectionalLight");
  51. lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
  52. Light@ light = lightNode.CreateComponent("Light");
  53. light.lightType = LIGHT_DIRECTIONAL;
  54. light.castShadows = true;
  55. light.shadowBias = BiasParameters(0.00005f, 0.5f);
  56. // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
  57. light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
  58. // Load Jack animated model
  59. jackNode_ = scene_.CreateChild("Jack");
  60. jackNode_.rotation = Quaternion(0.0f, 270.0f, 0.0f);
  61. AnimatedModel@ jack = jackNode_.CreateComponent("AnimatedModel");
  62. jack.model = cache.GetResource("Model", "Models/Jack.mdl");
  63. jack.material = cache.GetResource("Material", "Materials/Jack.xml");
  64. jack.castShadows = true;
  65. // Create animation controller and play walk animation
  66. jackAnimCtrl_ = jackNode_.CreateComponent("AnimationController");
  67. jackAnimCtrl_.PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0f);
  68. // We need to attach two inverse kinematic effectors to Jack's feet to
  69. // control the grounding.
  70. leftFoot_ = jackNode_.GetChild("Bip01_L_Foot", true);
  71. rightFoot_ = jackNode_.GetChild("Bip01_R_Foot", true);
  72. leftEffector_ = leftFoot_.CreateComponent("IKEffector");
  73. rightEffector_ = rightFoot_.CreateComponent("IKEffector");
  74. // Control 2 segments up to the hips
  75. leftEffector_.chainLength = 2;
  76. rightEffector_.chainLength = 2;
  77. // For the effectors to work, an IKSolver needs to be attached to one of
  78. // the parent nodes. Typically, you want to place the solver as close as
  79. // possible to the effectors for optimal performance. Since in this case
  80. // we're solving the legs only, we can place the solver at the spine.
  81. Node@ spine = jackNode_.GetChild("Bip01_Spine", true);
  82. solver_ = spine.CreateComponent("IKSolver");
  83. // Disable auto-solving, which means we can call Solve() manually.
  84. solver_.autoSolve = false;
  85. // When this is enabled, the solver will use the current positions of the
  86. // nodes in the skeleton as its basis every frame. If you disable this, then
  87. // the solver will store the initial positions of the nodes once and always
  88. // use those positions for calculating solutions.
  89. // With animated characters you generally want to continuously update the
  90. // initial positions.
  91. solver_.updatePose = true;
  92. // Create the camera.
  93. cameraRotateNode_ = scene_.CreateChild("CameraRotate");
  94. cameraNode = cameraRotateNode_.CreateChild("Camera");
  95. cameraNode.CreateComponent("Camera");
  96. // Set an initial position for the camera scene node above the plane
  97. cameraNode.position = Vector3(0.0f, 0.0f, -4.0f);
  98. cameraRotateNode_.position = Vector3(0.0f, 0.4f, 0.0f);
  99. pitch = 20.0f;
  100. yaw = 50.0f;
  101. }
  102. void CreateInstructions()
  103. {
  104. // Construct new Text object, set string to display and font to use
  105. Text@ instructionText = ui.root.CreateChild("Text");
  106. instructionText.text = "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";
  107. instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
  108. // Position the text relative to the screen center
  109. instructionText.horizontalAlignment = HA_CENTER;
  110. instructionText.verticalAlignment = VA_CENTER;
  111. instructionText.SetPosition(0, ui.root.height / 4);
  112. }
  113. void SetupViewport()
  114. {
  115. // 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
  116. // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
  117. // use, but now we just use full screen and default render path configured in the engine command line options
  118. Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
  119. renderer.viewports[0] = viewport;
  120. }
  121. void UpdateCameraAndFloor(float timeStep)
  122. {
  123. // Do not move if the UI has a focused element (the console)
  124. if (ui.focusElement !is null)
  125. return;
  126. // Mouse sensitivity as degrees per pixel
  127. const float MOUSE_SENSITIVITY = 0.1f;
  128. // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
  129. if (input.mouseButtonDown[MOUSEB_LEFT])
  130. {
  131. IntVector2 mouseMove = input.mouseMove;
  132. yaw += MOUSE_SENSITIVITY * mouseMove.x;
  133. pitch += MOUSE_SENSITIVITY * mouseMove.y;
  134. pitch = Clamp(pitch, -90.0f, 90.0f);
  135. }
  136. if (input.mouseButtonDown[MOUSEB_RIGHT])
  137. {
  138. IntVector2 mouseMoveInt = input.mouseMove;
  139. Vector2 mouseMove = Matrix2(
  140. -Cos(yaw), Sin(yaw),
  141. Sin(yaw), Cos(yaw)
  142. ) * Vector2(mouseMoveInt.y, -mouseMoveInt.x);
  143. floorPitch_ += MOUSE_SENSITIVITY * mouseMove.x;
  144. floorPitch_ = Clamp(floorPitch_, -90.0f, 90.0f);
  145. floorRoll_ += MOUSE_SENSITIVITY * mouseMove.y;
  146. }
  147. if (input.keyPress[KEY_SPACE])
  148. {
  149. floorPitch_ = 0.0f;
  150. floorRoll_ = 0.0f;
  151. }
  152. if (input.keyPress[KEY_D])
  153. {
  154. drawDebug_ = !drawDebug_;
  155. }
  156. // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
  157. cameraRotateNode_.rotation = Quaternion(pitch, yaw, 0.0f);
  158. floorNode_.rotation = Quaternion(floorPitch_, 0.0f, floorRoll_);
  159. }
  160. void SubscribeToEvents()
  161. {
  162. // Subscribe HandleUpdate() function for processing update events
  163. SubscribeToEvent("Update", "HandleUpdate");
  164. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
  165. SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished");
  166. }
  167. void HandleUpdate(StringHash eventType, VariantMap& eventData)
  168. {
  169. // Take the frame time step, which is stored as a float
  170. float timeStep = eventData["TimeStep"].GetFloat();
  171. // Move the camera, scale movement with time step
  172. UpdateCameraAndFloor(timeStep);
  173. }
  174. void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
  175. {
  176. if (drawDebug_)
  177. solver_.DrawDebugGeometry(false);
  178. }
  179. void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
  180. {
  181. Vector3 leftFootPosition = leftFoot_.worldPosition;
  182. Vector3 rightFootPosition = rightFoot_.worldPosition;
  183. // Cast ray down to get the normal of the underlying surface
  184. PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(Ray(leftFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
  185. if (result.body !is null)
  186. {
  187. // Cast again, but this time along the normal. Set the target position
  188. // to the ray intersection
  189. result = scene_.physicsWorld.RaycastSingle(Ray(leftFootPosition + result.normal, -result.normal), 2);
  190. // The foot node has an offset relative to the root node
  191. float footOffset = leftFoot_.worldPosition.y - jackNode_.worldPosition.y;
  192. leftEffector_.targetPosition = result.position + result.normal * footOffset;
  193. // Rotate foot according to normal
  194. leftFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
  195. }
  196. // Same deal with the right foot
  197. result = scene_.physicsWorld.RaycastSingle(Ray(rightFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
  198. if (result.body !is null)
  199. {
  200. result = scene_.physicsWorld.RaycastSingle(Ray(rightFootPosition + result.normal, -result.normal), 2);
  201. float footOffset = rightFoot_.worldPosition.y - jackNode_.worldPosition.y;
  202. rightEffector_.targetPosition = result.position + result.normal * footOffset;
  203. rightFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
  204. }
  205. solver_.Solve();
  206. }
  207. // Create XML patch instructions for screen joystick layout specific to this sample app
  208. String patchInstructions = "";