I’ve tried to port a Unity script found here.
Demo here.
It roughly works as expected but not perfect. As I’m totally unfamiliar with Unity I may have missed something or made wrong assumptions.
If someone familiar with Unity could check would be great.
[spoiler][code]
#include “Scripts/Utilities/Sample.as”
#include “Scripts/IKControl.as”
// IK stuff
Node@ upperArmL;
Node@ upperArmR;
float MOVE_SPEED = 1.0f; // Movement speed as world units per second
float MOUSE_SENSITIVITY = 0.1f; // Mouse sensitivity as degrees per pixel
String character = “Jack”;
void Start()
{
SampleStart();
CreateScene();
CreateUI();
SubscribeToEvents();
}
void CreateScene()
{
// SCENE ROOT
scene_ = Scene();
scene_.CreateComponent(“Octree”);
scene_.CreateComponent(“PhysicsWorld”);
scene_.CreateComponent(“DebugRenderer”);
// FLOOR
Node@ floorNode = scene_.CreateChild("Plane");
floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
floorNode.scale = Vector3(100.0f, 1.0f, 100.0f);
StaticModel@ planeObject = floorNode.CreateComponent("StaticModel");
planeObject.model = cache.GetResource("Model", "Models/Box.mdl");
planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
RigidBody@ body = floorNode.CreateComponent("RigidBody");
CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
shape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
// ZONE
Node@ zoneNode = scene_.CreateChild("Zone");
Zone@ zone = zoneNode.CreateComponent("Zone");
zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
zone.fogColor = Color(0.5f, 0.5f, 0.7f);
zone.fogStart = 100.0f;
zone.fogEnd = 300.0f;
// LIGHT
Node@ lightNode = scene_.CreateChild("DirectionalLight");
lightNode.direction = Vector3(0.6f, -1.0f, -0.8f);
Light@ light = lightNode.CreateComponent("Light");
light.lightType = LIGHT_DIRECTIONAL;
light.castShadows = true;
light.shadowBias = BiasParameters(0.00025f, 0.5f);
// Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
// CAMERA
cameraNode = scene_.CreateChild("Camera");
Camera@ camera = cameraNode.CreateComponent("Camera");
camera.farClip = 300.0f;
cameraNode.position = Vector3(0.0f, 1.2f, 3.5f);
cameraNode.rotation = Quaternion(0.0f, 180.0f, 0.0f);
// VIEWPORT
Viewport@ viewport = Viewport(scene_, camera);
renderer.viewports[0] = viewport;
// BALLS (target position to reach)
Node@ ball = scene_.CreateChild("Ball");
ball.SetScale(0.1f);
ball.position = Vector3(-0.5f, 1.5f, 0.25f);
StaticModel@ ballModel = ball.CreateComponent("StaticModel");
ballModel.model = cache.GetResource("Model", "Models/Sphere.mdl");
Node@ ball2 = ball.Clone();
ball2.name = "Ball2";
ball2.position = Vector3(0.5f, 1.5f, 0.5f);
// CHARACTER & IK CHAINS
if (character == "Jack")
{
Node@ modelNode = scene_.CreateChild("Character");
AnimatedModel@ modelObject = modelNode.CreateComponent("AnimatedModel");
modelNode.CreateComponent("AnimationController"); // Create the animation controller
modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
upperArmL = modelNode.GetChild("Bip01_L_UpperArm", true);
upperArmR = modelNode.GetChild("Bip01_R_UpperArm", true);
// Take manual control of the arms' bones
Skeleton@ skel = modelObject.skeleton;
skel.GetBone("Bip01_L_UpperArm").animated = false;
skel.GetBone("Bip01_L_Forearm").animated = false;
skel.GetBone("Bip01_L_Hand").animated = false;
skel.GetBone("Bip01_R_UpperArm").animated = false;
skel.GetBone("Bip01_R_Forearm").animated = false;
skel.GetBone("Bip01_R_Hand").animated = false;
// Create the IK controllers at the top of the IK chains
IKControl@ ikLeft = cast<IKControl>(upperArmL.CreateScriptObject(scriptFile, "IKControl"));
ikLeft.armAxis = Vector3(0.0f, -1.0f, 0.0f);
ikLeft.target = ball;
IKControl@ ikRight = cast<IKControl>(upperArmR.CreateScriptObject(scriptFile, "IKControl"));
ikRight.armAxis = Vector3(0.0f, 1.0f, 0.0f);
ikRight.target = ball2;
}
else
{
Node@ modelNode = scene_.InstantiateXML(cache.GetFile("Assets/" + character + "/Objects/" + character + ".xml"), Vector3(0.0f, 0.5f, 0.0f), Quaternion(0.0f, 0.0f, 0.0f));
modelNode.name = "Character";
modelNode.SetScale(0.9f);
AnimatedModel@ modelObject = modelNode.GetComponent("AnimatedModel");
modelNode.CreateComponent("AnimationController"); // Create the animation controller
upperArmL = modelNode.GetChild("UpperArm.L", true);
upperArmR = modelNode.GetChild("UpperArm.R", true);
// Take manual control of the arms' bones
// Skeleton@ skel = modelObject.skeleton;
// skel.GetBone(“UpperArm.L”).animated = false;
// skel.GetBone(“Forearm.L”).animated = false;
// skel.GetBone(“Hand.L”).animated = false;
// skel.GetBone(“UpperArm.R”).animated = false;
// skel.GetBone(“Forearm.R”).animated = false;
// skel.GetBone(“Hand.R”).animated = false;
// Create the IK controllers at the top of the IK chains
IKControl@ ikLeft = cast<IKControl>(upperArmL.CreateScriptObject(scriptFile, "IKControl"));
ikLeft.armAxis = Vector3(0.0f, -1.0f, 0.0f);
ikLeft.target = ball;
IKControl@ ikRight = cast<IKControl>(upperArmR.CreateScriptObject(scriptFile, "IKControl"));
ikRight.armAxis = Vector3(0.0f, 1.0f, 0.0f);
ikRight.target = ball2;
}
}
void CreateUI()
{
// Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
// control the camera, and when visible, it will point the raycast target
Cursor@ cursor = Cursor();
cursor.SetStyleAuto(cache.GetResource(“XMLFile”, “UI/DefaultStyle.xml”));
ui.cursor = cursor;
// Set starting position of the cursor at the rendering window center
cursor.SetPosition(graphics.width / 2, graphics.height / 2);
// Construct new Text object, set string to display and font to use
Text@ instructionText = ui.root.CreateChild("Text");
instructionText.text = "Use directional keys & mouse wheel to move the targets\n" "RMB to rotate around the character\n" "F5 to save scene, F7 to load\n" "Space to toggle debug geometry";
instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
instructionText.textAlignment = HA_CENTER;
// Position the text relative to the screen center
instructionText.horizontalAlignment = HA_CENTER;
instructionText.verticalAlignment = VA_CENTER;
instructionText.SetPosition(0, ui.root.height / 4);
}
void SubscribeToEvents()
{
SubscribeToEvent(“Update”, “HandleUpdate”);
SubscribeToEvent(“PostRenderUpdate”, “HandlePostRenderUpdate”);
}
void MoveCamera(float timeStep)
{
// Right mouse button controls mouse cursor visibility: hide when pressed
ui.cursor.visible = !input.mouseButtonDown[MOUSEB_RIGHT];
// Do not move if the UI has a focused element (the console)
if (ui.focusElement !is null)
return;
// Rotate camera around model when the cursor is hidden (ie when right mouse click)
if (!ui.cursor.visible)
{
yaw = yaw + MOUSE_SENSITIVITY * input.mouseMove.x;
cameraNode.RotateAround(scene_.GetChild("Character", true).position, Quaternion(0.0f, yaw * 0.1, 0.0f), TS_WORLD);
}
// Save the scene
if (input.keyPress[KEY_F5])
{
File saveFile(fileSystem.programDir + "Data/Scenes/IKControlDemo.xml", FILE_WRITE);
scene_.SaveXML(saveFile);
}
// Load the scene
if (input.keyPress[KEY_F7])
{
File loadFile(fileSystem.programDir + "Data/Scenes/IKControlDemo.xml", FILE_READ);
scene_.LoadXML(loadFile);
}
// Toggle debug geometry with space
if (input.keyPress[KEY_SPACE])
drawDebug = !drawDebug;
}
void MoveTarget(float timeStep)
{
// Read directional keys and move the target to the corresponding direction if they are pressed
Node@ targetBall = scene_.GetChild(“Ball”, true);
Node@ targetBall2 = scene_.GetChild(“Ball2”, true);
Vector3 move = Vector3(0.0f, 0.0f, 0.0f);
if (input.keyDown[KEY_UP]) move += Vector3(0.0f, 1.0f, 0.0f);
if (input.keyDown[KEY_DOWN]) move += Vector3(0.0f, -1.0f, 0.0f);
if (input.keyDown[KEY_LEFT]) move += Vector3(1.0f, 0.0f, 0.0f);
if (input.keyDown[KEY_RIGHT]) move += Vector3(-1.0f, 0.0f, 0.0f);
// Move target in the Z-axis using mouse wheel
if (input.mouseMoveWheel != 0) move += Vector3(0.0f, 0.0f, -input.mouseMoveWheel * 2.0f);
if (!move.Equals(Vector3(0.0f, 0.0f, 0.0f)))
{
targetBall.Translate(move * MOVE_SPEED * timeStep);
targetBall2.position = targetBall.position * Vector3(-1.0f, 1.0f, 1.0f);
}
}
void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
float timeStep = eventData[“TimeStep”].GetFloat();
MoveCamera(timeStep);
MoveTarget(timeStep);
// Drag IK effector with left mouse button click
float DRAG_SENSITIVITY = 0.01f;
// Play animation
AnimationController@ animCtrl = scene_.GetChild("Character", true).GetComponent("AnimationController");
if (character == "Jack")
animCtrl.PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2);
// else
// animCtrl.PlayExclusive(“Assets/” + character + “/Models/Run.ani”, 0, true, 0.2);
}
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
{
if (drawDebug) renderer.DrawDebugGeometry(false);
}
// Create XML patch instructions for screen joystick layout specific to this sample app
String patchInstructions = “”;
[/code][/spoiler]
[spoiler][code]
// Script Object is parented to forearm
// node
// > armIK
// > armRotation
class IKControl : ScriptObject
{
Node@ forearm; //var forearm : Transform;
Node@ hand; //var hand : Transform;
Node@ target; // var target : Transform;
Vector3 armAxis; // Arm rotation axis. Depends on the way the skeleton has been authored.
bool slerp = false;
// float transition = 1.0;
float elbowAngle = 0.0; // ???
Node@ armIK; //private var armIK : Transform;
Node@ armRotation; //private var armRotation : Transform;
float upperArmLength;
float forearmLength;
float armLength;
void Start()
{
forearm = node.children[0];
if (forearm is null)
return;
hand = forearm.children[0];
if (hand is null)
return;
// var armIKGameObject = new GameObject(“Arm IK”);
armIK = scene.CreateChild(“ArmIK”); //armIK = armIKGameObject.transform;
armIK.parent = node; // Parent to shoulder joint (UpperArm)
// var armRotationGameObject = new GameObject(“Arm Rotation”);
armRotation = scene.CreateChild(“ArmRotation”); //armRotation = armRotationGameObject.transform;
armRotation.parent = armIK;
upperArmLength = (forearm.worldPosition - node.worldPosition).length; //Vector3.Distance(transform.position, forearm.position);
forearmLength = (hand.worldPosition - forearm.worldPosition).length; //Vector3.Distance(forearm.position, hand.position);
armLength = upperArmLength + forearmLength;
// Subscribe to the SceneDrawableUpdateFinished event which is triggered after the animations have been updated, so we can apply IK to override them
SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished");
}
void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData) //function LateUpdate()
{
if (target is null || forearm is null || hand is null || armAxis.IsNaN())
return;
// Store rotation before IK
Quaternion storeUpperArmRotation = node.worldRotation;
Quaternion storeForearmRotation = forearm.worldRotation;
// Upper Arm (node) looks target
armIK.worldPosition = node.worldPosition;
armIK.LookAt(forearm.worldPosition);
armRotation.worldPosition = node.worldPosition;
armRotation.worldRotation = node.worldRotation;
armIK.LookAt(target.position);
node.worldRotation = armRotation.worldRotation;
// Upper Arm IK angle
float targetDistance = (target.position - node.worldPosition).length; //Vector3.Distance(transform.position, target.position);
targetDistance = Min(targetDistance, armLength - 0.00001); //Mathf.Min(targetDistance, armLength - 0.00001);
float adjacent = ((upperArmLength * upperArmLength) - (forearmLength * forearmLength) + (targetDistance * targetDistance)) / (2 * targetDistance);
float angle = Acos(adjacent / upperArmLength); //Mathf.Acos(adjacent / upperArmLength) * Mathf.Rad2Deg;
node.RotateAround(node.worldPosition, Quaternion(angle, armAxis), TS_WORLD); //node.RotateAround(transform.position, transform.forward, -angle);
// Forearm looks target
armIK.worldPosition = forearm.worldPosition;
armIK.LookAt(hand.worldPosition);
armRotation.worldPosition = forearm.worldPosition;
armRotation.worldRotation = forearm.worldRotation;
armIK.LookAt(target.position);
forearm.worldRotation = armRotation.worldRotation;
// Elbow angle
node.RotateAround(node.worldPosition, Quaternion(elbowAngle, target.position - node.worldPosition), TS_WORLD); //node.RotateAround(node.position, target.position - node.position, elbowAngle);
// Transition IK rotations with animation rotation.
if (slerp)
{
float transition = eventData["TimeStep"].GetFloat() * 8; //Mathf.Clamp01(transition);
node.worldRotation = storeUpperArmRotation.Slerp(node.worldRotation, transition); //Quaternion.Slerp(storeUpperArmRotation, node.rotation, transition);
forearm.worldRotation = storeForearmRotation.Slerp(forearm.worldRotation, transition); //Quaternion.Slerp(storeForearmRotation, forearm.rotation, transition);
}
}
}
[/code][/spoiler]