Animation and Games Development 242-515, Semester 1, 2014-2015 8. Rotations • Objective o explain the main types of object rotation, with the help of jME examples 242-515 AGD: 8. Rotations 1 Overview 1. 2. 3. 4. 5. 6. 7. Rotation Forms Matricies Euler Angles Multiple Rotations Axis-angle Rotation Quaternions Why use Quaternions? 242-515 AGD: 8. Rotations 2 1. Rotations Forms • There are four main ways of rotating a 3D object: o o o o matricies euler angles axis-angles quaternions • jME supports all of these, which I'll demonstrate with variations of the Sinbad.java application, which displays a 3D monster model. 242-515 AGD: 8. Rotations 3 Sinbad.java • The model is facing along the +z axis, towards the camera. It is centered at the origin. +y +x +z 242-515 AGD: 8. Rotations 4 Partial Code public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); } // end of simpleInitApp() I'll be explaining directional light and mesh models later. 242-515 AGD: 8. Rotations 5 2. Matricies • I described the 4x4 homogenous matrices for rotation about the x-, y-, and z- axes in part 7. M z -rotate cos sin 0 0 sin 0 0 cos 0 0 0 1 0 0 0 1 Rotates around the z-axis through the angle θ 242-515 AGD: 8. Rotations 6 • Similar matrices for rotations about x-, y- axes M x -rotate M y -rotate 242-515 AGD: 8. Rotations 1 0 0 0 0 cos sin 0 0 sin cos 0 0 0 0 1 0 sin 0 1 0 0 0 cos 0 0 0 1 cos 0 sin 0 7 • The Spatial class has numerous rotation methods. • The class includes: o Spatial.setLocalRotation(Matrix3f rotation) o It sets the local rotation of this node using a Matrix3f object. o The contents of the Matrix3f object should be the top left 3x3 part of the homogenous 4x4 matrix. 242-515 AGD: 8. Rotations 8 Rotating 45 degs around the z-axis • The rotation uses the cosine and sine of 45 degrees (in radians): M z -rotate 242-515 AGD: 8. Rotations cos 45 sin 45 sin 45 cos 45 0 0 0 0 0 0 0 0 1 0 0 1 9 RotMatrix.java Result 45 degrees around the z-axis 242-515 AGD: 8. Rotations 10 RotMatrix.java (partial) public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); // create rotation matrix for 45 degs around z-axis float cos45 = (float) Math.cos(Math.toRadians(45)); float sin45 = (float) Math.sin(Math.toRadians(45)); Matrix3f mat = new Matrix3f(Matrix3f.IDENTITY); mat.setColumn(0, new Vector3f(cos45, sin45, 0)); mat.setColumn(1, new Vector3f(-sin45, cos45, 0)); the new part player.setLocalRotation(mat); } // end of simpleInitApp() 242-515 AGD: 8. Rotations 11 • jME has a FastMath class with faster (but less accurate) trigonometry constants and methods than those in Java's Math class. • The matrix code can be rewritten using FastMath: // create rotation matrix for 45 degs around z-axis float cos45 = FastMath.cos(FastMath.QUARTER_PI); float sin45 = FastMath.sin(FastMath.QUARTER_PI); the new part Matrix3f mat = new Matrix3f(Matrix3f.IDENTITY); mat.setColumn(0, new Vector3f(cos45, sin45, 0)); mat.setColumn(1, new Vector3f(-sin45, cos45, 0)); : 242-515 AGD: 8. Rotations 12 3. Euler Angles • Euler angles express rotations in terms of angles around the object's local x-, y-, and z- axes. • There's no need to build a matrix, just specify the three angles. • Traditionally, these angles are called: o yaw – a rotation around the y-axis +y o pitch – a rotation around x-axis o roll – a rotation around the z-axis +z 242-515 AGD: 8. Rotations +x 13 Rotating 45 degs around the z-axis (again) • The relevant Spatial method for Euler angle rotations is: o Spatial.rotate(float x, float y, float z) o it rotates the spatial by the x-, y- and z- axis angles (in radians), in the local coordinate space o the arguments are not (yaw, roll, pitch) even though that's what the documentation says • The RotRoll.java application applies a 45 degree roll (rotation around the z-axis) using euler angles instead of a matrix. 242-515 AGD: 8. Rotations 14 RotRoll.java Result • The same result as RotMatrix.java 242-515 AGD: 8. Rotations 15 Partial Code RotRoll.java public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); No rootNode.attachChild(player); matrix code required. player.rotate(0, 0, (float)Math.toRadians(45)); // player.rotate(0, 0, FastMath.QUARTER_PI); // roll: rotate around z-axis } // end of simpleInitApp() 242-515 AGD: 8. Rotations 16 Rotating the Local Coord. Space • An euler angle rotation rotates the local coordinate space (and everything inside it). • This means that the x-, y-, and z- axes of the local space will no longer match up with the x-, y-, and zaxes of the global space. local coord. space y x global coord. space y z x z 242-515 AGD: 8. Rotations global y spatial.rotate() coord. space z local coord. space x 17 Turning Around the y-axis • Make the model turn constantly around its y-axis. RotLoop.java (very similar to part 6's HelloLoop.java) 242-515 AGD: 8. Rotations 18 Partial Code RotLoop.java • The constant change requires the call to rotate() to be moved to the simpleUpdate() method inherited from Simple Application. private Spatial player; // now global public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); } // end of simpleInitApp() 242-515 AGD: 8. Rotations 19 public void simpleUpdate(float tpf) // update action, called from game loop automatically // the player is rotated around the y-axis { player.rotate(0, 2*tpf, 0); } 242-515 AGD: 8. Rotations 20 4. Multiple Rotations • An euler angle rotation rotates the local coordinate space. • This can become confusing when multiple euler rotations are applied to a model. • For example: player.rotate(0, FastMath.HALF_PI, 0); player.rotate(0, 0, FastMath.HALF_PI); player.rotate(FastMath.HALF_PI, 0, 0); // y-axis rotation // z // x • It's confusing since the x-, y-, and z-axes of the local coordinate space do not match the global space axes after the first rotation. 242-515 AGD: 8. Rotations 21 rotate(0, FastMath.HALF_PI, 0); 90 degree y-axis rotation y y x x z z rotate(0, 0, FastMath.HALF_PI); 90 degree z-axis rotation x y x z z y rotate(FastMath.HALF_PI,0,0); 90 degree x-axis rotation 242-515 AGD: 8. Rotations 22 RotationTest1 Result 242-515 AGD: 8. Rotations 23 Partial Code RotationTest1.java public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); player.rotate(0, FastMath.HALF_PI, 0); // y-axis rotation player.rotate(0, 0, FastMath.HALF_PI); // z player.rotate(FastMath.HALF_PI, 0, 0); // x } // end of simpleInitApp() 242-515 AGD: 8. Rotations 24 More Complex rotate() Calls • The complexity of multiple rotate() calls is made worse, if several euler angle rotations are combined into a single rotate() call. • For example: player.rotate(0, FastMath.HALF_PI, 0); player.rotate(0, 0, FastMath.HALF_PI); player.rotate(FastMath.HALF_PI, 0, 0); // y-axis rotation // z // x • is the same as player.rotate(FastMath.HALF_PI, FastMath.HALF_PI, FastMath.HALF_PI); • You must remember the hidden order: y, z, x. o this is not the same as (x, y, z) or (z, y, x) or others since rotation is non-commutative. 242-515 AGD: 8. Rotations 25 • e.g: reorder the rotate() lines in RotationsTest1.java: player.rotate(FastMath.HALF_PI, 0, 0); // x was last, now first player.rotate(0, FastMath.HALF_PI, 0); // y player.rotate(0, 0, FastMath.HALF_PI); // z • Result: z x y 242-515 AGD: 8. Rotations 26 5. Axis-angle Rotation • An axis-angle rotation is a rotation around a userspecified vector (a rotation axis) o this is more flexible than an euler angle rotation which fixes the vector to be the x-, y-, or z- axis 242-515 AGD: 8. Rotations 27 RotAxis.java • Rotate the model 90 degrees around the y=x line y y z 242-515 AGD: 8. Rotations x x z 28 Partial Code RotAxis.java public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); angle axis // rotate 90 degrees around y=x line Vector3f xyVec = new Vector3f(1.0f, 1.0f, 0).normalize(); Quaternion q1 = new Quaternion().fromAngleAxis(FastMath.HALF_PI, xyVec); player.rotate(q1); } In jME an axis-angle must be executed as a quaternion 242-515 AGD: 8. Rotations 29 Euler's Rotation Theorem • Euler's theorem: any rotation can be written as a single rotation about an axis. • This means that we can rotate a model to any position with one axis-angle rotation o but working out the necessary axis and angle can be tricky! 242-515 AGD: 8. Rotations 30 6. Quaternions • Quaternions are an extension to complex numbers. A quaternion is made up of 4 values: o one is a scalar (called w), and the other three are a vector in 3D complex number space (i, j, k) • A quaternion (i, j, k, w) can be viewed as a point on the surface of a 4D sphere o which I'll draw as a 3D sphere (i, j, k, w) 242-515 AGD: 8. Rotations 31 Quaternions and Axis-Angles • There's a simple mapping between an axis-angle and a quaternion. • If the axis-angle is [x, y, z, θ] then the corresponding quaternion (i, j, k, w) is equal to: o ( sin(θ/2)*x, sin(θ/2)*y, sin(θ/2)*z, cos(θ/2) ) • Example: a rotation of 90 degrees around the y-axis o axis-angle = [0, 1, 0, π/2] o quaternion = (0, sqrt(2)/2, 0, sqrt(2)/2 ) • since cos(45) = sin(45) = sqrt(2)/2 242-515 AGD: 8. Rotations 32 Quaternions in jME public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); Spatial player = assetManager.loadModel( "Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); // rotate 90 degs around y-axis float trig = FastMath.sqrt(2)/2; Quaternion q1 = new Quaternion(0, trig, 0, trig); // x, y, z, w // carry out y-axis rotation of 90 degrees player.rotate(q1); } // end of simpleInitApp() 242-515 AGD: 8. Rotations new part 33 Combining Rotations • Two quaternion rotations are combined ('added together') by multiplication o e.g. carry out two 90 degree rotations around the y-axis: // rotate 90 degs around y-axis float trig = FastMath.sqrt(2)/2; Quaternion q1 = new Quaternion(0, trig, 0, trig); // x, y, z, w // carry out two rotations of 90 degrees Quaternion q2 = q1.mult(q1); player.rotate(q2); RotQuats.java 242-515 AGD: 8. Rotations 34 7. Why Use Quaternions? • We often want to smoothly rotate a model from one position to another o called rotational interpolation • Calculating a smooth rotational interpolation between two matrix rotations, two euler angles, or two axis-angles is difficult o but rotational interpolation is easy between quaternions 242-515 AGD: 8. Rotations 35 Slerping • Rotating between two quaternions is equivalent to following the shortest path of the great circle arc between them o a great circle arc is the distance over the sphere's surface between two points o called slerping (spherical linear interpolatation) 242-515 AGD: 8. Rotations 36 Slerping in jME • We want to smoothly rotate the model between the following two positions: rotation of -90 degrees around the z-axis 242-515 AGD: 8. Rotations rotation of 180 degrees around the y = -x line 37 rotate -90 degree around z-axis y x z z y x z y x z 242-515 AGD: 8. Rotations x rotate 180 degrees around the y= -x line y 38 Implementation RotSlerp.java • The simpleInitApp() method creates the two positions as quaternion rotations. • The simpleUpdate() method uses jME's Quaternion.slerp() to smoothly interpolate between the two quaternions o this will cause the model to slowly rotate between the two positions, updated with each call to simpleUpdate() 242-515 AGD: 8. Rotations 39 Execution 242-515 AGD: 8. Rotations RotSlerp.java 40 RotSlerp Code // globals private static final float SLERP_STEP = 0.025f; private Spatial player; private Quaternion rightFrontQ, leftBackQ, slerpQ; private float currSlerp = 0f; private boolean isForward = true; public void simpleInitApp() { DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1, 0, -2).normalizeLocal()); sun.setColor(ColorRGBA.White); rootNode.addLight(sun); player = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); : 242-515 AGD: 8. Rotations 41 : // rotated -90 degs around z-axis Vector3f zVec = new Vector3f(0, 0, 1.0f); rightFrontQ = new Quaternion().fromAngleAxis(-FastMath.HALF_PI, zVec); // rotated 180 degs around y=-x line Vector3f negxyVec = new Vector3f(-1.0f, 1.0f, 0).normalize(); leftBackQ = new Quaternion().fromAngleAxis(FastMath.PI, negxyVec); slerpQ = new Quaternion(); player.rotate(rightFrontQ); } // end of simpleInitApp() 242-515 AGD: 8. Rotations // player starts at rightFront 42 public void simpleUpdate(float tpf) // update action, called from game loop automatically // the player is slerped between the two quaternions { if (isForward) slerpQ is a rotation currSlerp += SLERP_STEP; between the other two else currSlerp -= SLERP_STEP; slerpQ.slerp(rightFrontQ, leftBackQ, currSlerp); player.setLocalRotation(slerpQ); if ((currSlerp >= 1f) || (currSlerp <= 0f)) isForward = !isForward; } // end of simpleUpdate() 242-515 AGD: 8. Rotations varies from 0 to 1 and back to 0 43