| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- -- Ribbon trail demo.
- -- This sample demonstrates how to adjust the position of animated feet so they match the ground's angle using IK.
- require "LuaScripts/Utilities/Sample"
- local jackAnimCtrl_
- local cameraRotateNode_
- local floorNode_
- local leftFoot_
- local rightFoot_
- local leftEffector_
- local rightEffector_
- local solver_
- local floorPitch_ = 0.0
- local floorRoll_ = 0.0
- local drawDebug_ = false
- function Start()
- cache.autoReloadResources = true
- -- Execute the common startup for samples
- SampleStart()
- -- Create the scene content
- CreateScene()
- -- Create the UI content
- CreateInstructions()
- -- Setup the viewport for displaying the scene
- SetupViewport()
- -- Set the mouse mode to use in the sample
- SampleInitMouseMode(MM_RELATIVE)
- -- Hook up to the frame update events
- SubscribeToEvents()
- end
- function CreateScene()
- scene_ = Scene()
- -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
- scene_:CreateComponent("Octree")
- scene_:CreateComponent("DebugRenderer")
- scene_:CreateComponent("PhysicsWorld")
- -- Create scene node & StaticModel component for showing a static plane
- floorNode_ = scene_:CreateChild("Plane")
- floorNode_.scale = Vector3(50.0, 1.0, 50.0)
- local planeObject = floorNode_:CreateComponent("StaticModel")
- planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
- planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
- -- Set up collision, we need to raycast to determine foot height
- floorNode_:CreateComponent("RigidBody")
- local col = floorNode_:CreateComponent("CollisionShape")
- col:SetBox(Vector3(1, 0, 1))
- -- Create a directional light to the world.
- local lightNode = scene_:CreateChild("DirectionalLight")
- lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
- local light = lightNode:CreateComponent("Light")
- light.lightType = LIGHT_DIRECTIONAL
- light.castShadows = true
- light.shadowBias = BiasParameters(0.00005, 0.5)
- -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
- light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
- -- Load Jack animated model
- jackNode_ = scene_:CreateChild("Jack")
- jackNode_.rotation = Quaternion(0.0, 270.0, 0.0)
- jack = jackNode_:CreateComponent("AnimatedModel")
- jack.model = cache:GetResource("Model", "Models/Jack.mdl")
- jack.material = cache:GetResource("Material", "Materials/Jack.xml")
- jack.castShadows = true
- -- Create animation controller and play walk animation
- jackAnimCtrl_ = jackNode_:CreateComponent("AnimationController")
- jackAnimCtrl_:PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0)
- -- We need to attach two inverse kinematic effectors to Jack's feet to
- -- control the grounding.
- leftFoot_ = jackNode_:GetChild("Bip01_L_Foot", true)
- rightFoot_ = jackNode_:GetChild("Bip01_R_Foot", true)
- leftEffector_ = leftFoot_:CreateComponent("IKEffector")
- rightEffector_ = rightFoot_:CreateComponent("IKEffector")
- -- Control 2 segments up to the hips
- leftEffector_.chainLength = 2
- rightEffector_.chainLength = 2
- -- For the effectors to work, an IKSolver needs to be attached to one of
- -- the parent nodes. Typically, you want to place the solver as close as
- -- possible to the effectors for optimal performance. Since in this case
- -- we're solving the legs only, we can place the solver at the spine.
- local spine = jackNode_:GetChild("Bip01_Spine", true)
- solver_ = spine:CreateComponent("IKSolver")
- -- Two-bone solver is more efficient and more stable than FABRIK (but only
- -- works for two bones, obviously).
- solver_.algorithm = IKSolver.TWO_BONE
- -- Disable auto-solving, which means we can call Solve() manually.
- solver_.AUTO_SOLVE = false
- -- Only enable this so the debug draw shows us the pose before solving.
- -- This should NOT be enabled for any other reason (it does nothing and is
- -- a waste of performance).
- solver_.UPDATE_ORIGINAL_POSE = true
- -- Create the camera.
- cameraRotateNode_ = scene_:CreateChild("CameraRotate")
- cameraNode = cameraRotateNode_:CreateChild("Camera")
- cameraNode:CreateComponent("Camera")
- -- Set an initial position for the camera scene node above the plane
- cameraNode.position = Vector3(0.0, 0.0, -4.0)
- cameraRotateNode_.position = Vector3(0.0, 0.4, 0.0)
- pitch = 20.0
- yaw = 50.0
- end
- function CreateInstructions()
- -- Construct new Text object, set string to display and font to use
- local instructionText = ui.root:CreateChild("Text")
- 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")
- instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
- -- Position the text relative to the screen center
- instructionText.horizontalAlignment = HA_CENTER
- instructionText.verticalAlignment = VA_CENTER
- instructionText:SetPosition(0, ui.root.height / 4)
- end
- function SetupViewport()
- -- 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
- -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
- -- use, but now we just use full screen and default render path configured in the engine command line options
- local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
- renderer:SetViewport(0, viewport)
- end
- function UpdateCameraAndFloor(timeStep)
- -- Do not move if the UI has a focused element (the console)
- if ui.focusElement ~= nil then
- return
- end
- -- Mouse sensitivity as degrees per pixel
- local MOUSE_SENSITIVITY = 0.1
- -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
- if input:GetMouseButtonDown(MOUSEB_LEFT) then
- local mouseMove = input.mouseMove
- yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
- pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
- pitch = Clamp(pitch, -90.0, 90.0)
- end
- if input:GetMouseButtonDown(MOUSEB_RIGHT) then
- local mouseMoveInt = input.mouseMove
- local mouseMove = Vector2()
- mouseMove.x = -Cos(yaw) * mouseMoveInt.y - Sin(yaw) * mouseMoveInt.x
- mouseMove.y = Sin(yaw) * mouseMoveInt.y - Cos(yaw) * mouseMoveInt.x
- floorPitch_ = floorPitch_ + MOUSE_SENSITIVITY * mouseMove.x
- floorPitch_ = Clamp(floorPitch_, -90.0, 90.0)
- floorRoll_ = floorRoll_ + MOUSE_SENSITIVITY * mouseMove.y
- end
- if input:GetKeyPress(KEY_SPACE) then
- floorPitch_ = 0.0
- floorRoll_ = 0.0
- end
- if input:GetKeyPress(KEY_D) then
- drawDebug_ = not drawDebug_
- end
- -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
- cameraRotateNode_.rotation = Quaternion(pitch, yaw, 0.0)
- floorNode_.rotation = Quaternion(floorPitch_, 0.0, floorRoll_)
- end
- function SubscribeToEvents()
- -- Subscribe HandleUpdate() function for processing update events
- SubscribeToEvent("Update", "HandleUpdate")
- SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
- SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
- end
- function HandleUpdate(eventType, eventData)
- -- Take the frame time step, which is stored as a float
- local timeStep = eventData["TimeStep"]:GetFloat()
- -- Move the camera, scale movement with time step
- UpdateCameraAndFloor(timeStep)
- end
- function HandlePostRenderUpdate(eventType, eventData)
- if drawDebug_ then
- solver_:DrawDebugGeometry(false)
- end
- end
- function HandleSceneDrawableUpdateFinished(eventType, eventData)
- local physicsWorld = scene_:GetComponent("PhysicsWorld")
- local leftFootPosition = leftFoot_.worldPosition
- local rightFootPosition = rightFoot_.worldPosition
- -- Cast ray down to get the normal of the underlying surface
- local result = physicsWorld:RaycastSingle(Ray(leftFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
- if result.body then
- -- Cast again, but this time along the normal. Set the target position
- -- to the ray intersection
- local oppositeNormal = result.normal * -1
- result = physicsWorld:RaycastSingle(Ray(leftFootPosition + result.normal, oppositeNormal), 2)
- -- The foot node has an offset relative to the root node
- footOffset = leftFoot_.worldPosition.y - jackNode_.worldPosition.y
- leftEffector_.targetPosition = result.position + result.normal * footOffset
- -- Rotate foot according to normal
- leftFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
- end
- -- Same deal with the right foot
- result = physicsWorld:RaycastSingle(Ray(rightFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
- if result.body then
- local oppositeNormal = result.normal * -1
- result = physicsWorld:RaycastSingle(Ray(rightFootPosition + result.normal, oppositeNormal), 2)
- footOffset = rightFoot_.worldPosition.y - jackNode_.worldPosition.y
- rightEffector_.targetPosition = result.position + result.normal * footOffset
- rightFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
- end
- solver_:Solve()
- end
|