{"id":59,"date":"2022-05-11T10:19:54","date_gmt":"2022-05-11T10:19:54","guid":{"rendered":"https:\/\/noirccc.net\/blog\/?p=59"},"modified":"2024-06-21T12:05:25","modified_gmt":"2024-06-21T12:05:25","slug":"quaternions-rotation-axis-independence-and-indepent-axis-smoothing","status":"publish","type":"post","link":"https:\/\/noirccc.net\/blog\/posts\/59","title":{"rendered":"Quaternions, Rotation Axis Independence, Acceleration Interpolation"},"content":{"rendered":"\n<p><a data-type=\"URL\" data-id=\"https:\/\/github.com\/NoiRC256\/CameraWorks\" href=\"https:\/\/github.com\/NoiRC256\/CameraWorks\" target=\"_blank\" rel=\"noreferrer noopener\">CameraWorks<\/a> was a great opportunity to revisit and experiment on some implementations of camera motion behaviours.<\/p>\n\n\n\n<p>Rotation axis independence is a crucial aspect to consider when implementing free camera movement. Normally, FPS controls require camera yaw aligned to world plane&#8217;s vertical axis; While flight sim controls require complemetely independent pitch\/yaw\/roll. A good grasp of quaternion angle and vector math concepts is extremely beneficial for implementing these behaviours.<\/p>\n\n\n\n<p>Case A; To have pitch and yaw aligned to world plane, unaffected by roll: directly append delta rotation to current rotation, set as target rotation.<\/p>\n\n\n\n<p>Case B: To have free pitch, yaw, roll: simply multiply quaternion of current rotation with quaternion of delta rotation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>protected virtual void HandleRotationInput()\n{\n    Vector3 input = CalculateDesiredRotationInput();\n    smoothedDeltaPitch = Mathf.Lerp(smoothedDeltaPitch, input.x, Time.unscaledDeltaTime * lookSmooth);\n    smoothedDeltaYaw = Mathf.Lerp(smoothedDeltaYaw, input.y, Time.unscaledDeltaTime * lookSmooth);\n    smoothedDeltaRoll = Mathf.Lerp(smoothedDeltaRoll, input.z, Time.unscaledDeltaTime * rollSmooth);\n\n    if(lookByWorldPlane) {\n        curEuler += new Vector3(smoothedDeltaPitch, smoothedDeltaYaw, smoothedDeltaRoll);\n        targetQuaternion = Quaternion.Euler(curAngles);\n    } else {\n        deltaQuaternion = Quaternion.Euler(new Vector3(smoothedDeltaPitch, smoothedDeltaYaw, smoothedDeltaRoll));\n        targetQuaternion *= deltaQuaternion.\n    }\n}<\/code><\/pre>\n\n\n\n<p>Here independent axis smoothing is achieved by smoothing each axis of rotation input, before composing the final quaternion. Essentially, mouse acceleration.<\/p>\n\n\n\n<p>This may not be desirable in some situations &#8212; as the smoothing parameter changes, the same amount of rotation input may result in different amounts of final rotation. For more consistent control of rotation results, we want to first set a definitive target rotation based on rotation input, then smoothly rotate to that target rotation.<\/p>\n\n\n\n<p>Usually something like Quaternion.Slerp is sufficient for smoothing the final rotation, but we still need independent axis smoothing. This can be achieved through eulerangles.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>protected virtual void HandleRotationInput()\n{\n    Vector3 input = CalculateDesiredRotationInput();\n\n    if(lookByWorldPlane) {\n        curEuler += input;\n        targetEuler = curAngles;\n    } else {\n        targetRotation *= Quaternion.Euler(input);\n    }\n}<\/code><\/pre>\n\n\n\n<p>Then we can safely lerp to a well-defined target rotation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\nprotected virtual void UpdateRotation()\n{\n    if (LookByWorldPlane) {\n        smoothedEuler.x = Mathf.Lerp(smoothedEuler.x, targetEuler.x, LookSmooth * Time.unscaledDeltaTime);\n        smoothedEuler.y = Mathf.Lerp(smoothedEuler.y, targetEuler.y, LookSmooth * Time.unscaledDeltaTime);\n        smoothedEuler.z = Mathf.Lerp(smoothedEuler.z, targetEuler.z, RollSmooth * Time.unscaledDeltaTime);\n        transform.localEulerAngles = smoothedEuler;\n    } else {\n        smoothedQuaternion = Quaternion.Slerp(transform.localRotation, targetRotation, LookSmooth * Time.unscaledDeltaTime);\n        transform.localRotation = smoothedQuaternion;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Though independent axis smoothing for Case B is easier achieved with the previous method of smoothing mouse inputs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Notes from some experiments in CameraWorks regarding camera motion.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[54,43],"tags":[53],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/posts\/59"}],"collection":[{"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/comments?post=59"}],"version-history":[{"count":15,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/posts\/59\/revisions"}],"predecessor-version":[{"id":1216,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/posts\/59\/revisions\/1216"}],"wp:attachment":[{"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/media?parent=59"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/categories?post=59"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/noirccc.net\/blog\/wp-json\/wp\/v2\/tags?post=59"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}