├── InertiaAlgorithm.cs ├── LICENSE ├── PostInertializer.cs ├── PostInertializerTransitionProfile.cs └── README.md /InertiaAlgorithm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Unity.Mathematics; 6 | using static Unity.Mathematics.math; 7 | 8 | namespace Pd 9 | { 10 | public static class InertiaAlgorithm 11 | { 12 | static void SyncSide(ref quaternion quat, quaternion side) 13 | { 14 | if (dot(side, quat) < 0) 15 | quat.value = -quat.value; 16 | } 17 | 18 | public static float4 toAxisAngle(quaternion quat) 19 | { 20 | float4 q1 = quat.value; 21 | 22 | if (q1.w > 1) 23 | normalize(q1); 24 | float angle = 2 * acos(q1.w); 25 | float s = sqrt(1 - q1.w * q1.w); 26 | float3 axis; 27 | if (s < 0.001) 28 | { 29 | axis.x = q1.x; 30 | axis.y = q1.y; 31 | axis.z = q1.z; 32 | } 33 | else 34 | { 35 | axis.x = q1.x / s; // normalise axis 36 | axis.y = q1.y / s; 37 | axis.z = q1.z / s; 38 | } 39 | return float4(axis, angle); 40 | } 41 | 42 | 43 | 44 | 45 | public static float Inertialize(float x0, float v0, float dt, float tf, float t) 46 | { 47 | float tf1 = -5 * x0 / v0; 48 | if (tf1 > 0) 49 | tf = min(tf, tf1); 50 | 51 | t = min(t, tf); 52 | 53 | float tf2 = tf * tf; 54 | float tf3 = tf2 * tf; 55 | float tf4 = tf3 * tf; 56 | float tf5 = tf4 * tf; 57 | 58 | float a0 = (-8 * v0 * tf - 20 * x0) / (tf * tf); 59 | 60 | float A = -(a0 * tf2 + 6 * v0 * tf + 12 * x0) / (2 * tf5); 61 | float B = (3 * a0 * tf2 + 16 * v0 * tf + 30 * x0) / (2 * tf4); 62 | float C = -(3 * a0 * tf2 + 12 * v0 * tf + 20 * x0) / (2 * tf3); 63 | 64 | float t2 = t * t; 65 | float t3 = t2 * t; 66 | float t4 = t3 * t; 67 | float t5 = t4 * t; 68 | 69 | float xt = A * t5 + B * t4 + C * t3 + (a0 / 2) * t2 + v0 * t + x0; 70 | 71 | 72 | if (tf < 0.00001f) 73 | xt = 0; 74 | 75 | return xt; 76 | } 77 | 78 | public static float Inertialize(float prev, float curr, float target, float dt, float tf, float t) 79 | { 80 | float x0 = curr - target; 81 | float v0 = (curr - prev) / dt; 82 | 83 | return Inertialize(x0, v0, dt, tf, t); 84 | } 85 | 86 | public static float3 InertializeMagnitude(float3 prev, float3 curr, float3 target, float dt, float tf, float t) 87 | { 88 | float3 vx0 = curr - target; 89 | float3 vxn1 = prev - target; 90 | 91 | float x0 = length(vx0); 92 | 93 | float3 vx0_dir = x0 > 0.00001f ? (vx0 / x0) : length(vxn1) > 0.00001f ? normalize(vxn1) : float3(1, 0, 0); 94 | 95 | float xn1 = dot(vxn1, vx0_dir); 96 | float v0 = (x0 - xn1) / dt; 97 | 98 | float xt = Inertialize(x0, v0, dt, tf, t); 99 | 100 | float3 vxt = xt * vx0_dir + target; 101 | 102 | return vxt; 103 | } 104 | 105 | public static float3 InertializeDirect(float3 prev, float3 curr, float3 target, float dt, float tf, float t) 106 | { 107 | return target + float3( 108 | Inertialize(prev.x, curr.x, target.x, dt, tf, t), 109 | Inertialize(prev.y, curr.y, target.y, dt, tf, t), 110 | Inertialize(prev.z, curr.z, target.z, dt, tf, t)); 111 | } 112 | 113 | public static quaternion InertializeMagnitude(quaternion prev, quaternion curr, quaternion target, float dt, float tf, float t) 114 | { 115 | if (length(target) < 0.0001f) 116 | target = quaternion(0, 0, 0, 1); 117 | if (length(curr) < 0.0001f) 118 | curr = quaternion(0, 0, 0, 1); 119 | if (length(prev) < 0.0001f) 120 | prev = quaternion(0, 0, 0, 1); 121 | 122 | 123 | quaternion q0 = normalize(mul(curr, inverse(target))); 124 | quaternion qn1 = normalize(mul(prev, inverse(target))); 125 | 126 | float4 q0_aa = toAxisAngle(q0); 127 | 128 | float3 vx0 = q0_aa.xyz; 129 | float x0 = q0_aa.w; 130 | 131 | float xn1 = 2 * atan(dot(qn1.value.xyz, vx0) / qn1.value.w); 132 | 133 | float v0 = (x0 - xn1) / dt; 134 | 135 | float xt = Inertialize(x0, v0, dt, tf, t); 136 | quaternion qt = mul(Unity.Mathematics.quaternion.AxisAngle(vx0, xt), target); 137 | 138 | return normalize(qt); 139 | } 140 | 141 | public static quaternion InertializeDirect(quaternion prev, quaternion curr, quaternion target, float dt, float tf, float t) 142 | { 143 | SyncSide(ref prev, target); 144 | SyncSide(ref curr, target); 145 | 146 | return normalize(target.value + float4( 147 | Inertialize(prev.value.x, curr.value.x, target.value.x, dt, tf, t), 148 | Inertialize(prev.value.y, curr.value.y, target.value.y, dt, tf, t), 149 | Inertialize(prev.value.z, curr.value.z, target.value.z, dt, tf, t), 150 | Inertialize(prev.value.w, curr.value.w, target.value.w, dt, tf, t))); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, portalmk2 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /PostInertializer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using Unity.Collections; 6 | using Unity.Mathematics; 7 | using static Unity.Mathematics.math; 8 | using static Pd.InertiaAlgorithm; 9 | 10 | namespace Pd { 11 | 12 | [RequireComponent(typeof(Animator))] 13 | public class PostInertializer : MonoBehaviour 14 | { 15 | public enum EvaluateSpace { 16 | Local, 17 | Character, 18 | World 19 | } 20 | 21 | 22 | struct TransformState 23 | { 24 | public float3 PrevPosition; 25 | public float3 Position; 26 | 27 | public quaternion PrevRotation; 28 | public quaternion Rotation; 29 | 30 | public EvaluateSpace Space; 31 | public float BeginTime; 32 | public float EndTime; 33 | } 34 | 35 | 36 | private Animator _Animator; 37 | public Animator Animator { 38 | get { 39 | if (_Animator == null) 40 | _Animator = GetComponent(); 41 | return _Animator; 42 | } 43 | } 44 | 45 | 46 | public AvatarMask AvatarMask; 47 | 48 | public float BlendTime = 0.5f; 49 | 50 | 51 | #region AvatarMaskBodyPart Mapping 52 | public static readonly Dictionary AvatarBodyPartMapping = new Dictionary() { 53 | { AvatarMaskBodyPart.Body, new HumanBodyBones[]{ 54 | HumanBodyBones.Chest, 55 | HumanBodyBones.Hips, 56 | HumanBodyBones.Spine, 57 | HumanBodyBones.UpperChest } }, 58 | { AvatarMaskBodyPart.Head, new HumanBodyBones[]{ 59 | HumanBodyBones.Head, 60 | HumanBodyBones.Neck, 61 | HumanBodyBones.LeftEye, 62 | HumanBodyBones.RightEye, 63 | HumanBodyBones.Jaw } }, 64 | { AvatarMaskBodyPart.LeftArm, new HumanBodyBones[]{ 65 | HumanBodyBones.LeftUpperArm, 66 | HumanBodyBones.LeftLowerArm, 67 | HumanBodyBones.LeftHand} }, 68 | { AvatarMaskBodyPart.RightArm, new HumanBodyBones[]{ 69 | HumanBodyBones.RightUpperArm, 70 | HumanBodyBones.RightLowerArm, 71 | HumanBodyBones.RightHand} }, 72 | { AvatarMaskBodyPart.LeftLeg, new HumanBodyBones[]{ 73 | HumanBodyBones.LeftUpperLeg, 74 | HumanBodyBones.LeftLowerLeg, 75 | HumanBodyBones.LeftFoot, 76 | HumanBodyBones.LeftToes } }, 77 | { AvatarMaskBodyPart.RightLeg, new HumanBodyBones[]{ 78 | HumanBodyBones.RightUpperLeg, 79 | HumanBodyBones.RightLowerLeg, 80 | HumanBodyBones.RightFoot, 81 | HumanBodyBones.RightToes } }, 82 | { AvatarMaskBodyPart.LeftFingers, new HumanBodyBones[] { 83 | HumanBodyBones.LeftThumbProximal, 84 | HumanBodyBones.LeftThumbIntermediate, 85 | HumanBodyBones.LeftThumbDistal, 86 | HumanBodyBones.LeftIndexProximal, 87 | HumanBodyBones.LeftIndexIntermediate, 88 | HumanBodyBones.LeftIndexDistal, 89 | HumanBodyBones.LeftMiddleProximal, 90 | HumanBodyBones.LeftMiddleIntermediate, 91 | HumanBodyBones.LeftMiddleDistal, 92 | HumanBodyBones.LeftRingProximal, 93 | HumanBodyBones.LeftRingIntermediate, 94 | HumanBodyBones.LeftRingDistal, 95 | HumanBodyBones.LeftLittleProximal, 96 | HumanBodyBones.LeftLittleIntermediate, 97 | HumanBodyBones.LeftLittleDistal } }, 98 | { AvatarMaskBodyPart.RightFingers, new HumanBodyBones[] { 99 | HumanBodyBones.RightThumbProximal, 100 | HumanBodyBones.RightThumbIntermediate, 101 | HumanBodyBones.RightThumbDistal, 102 | HumanBodyBones.RightIndexProximal, 103 | HumanBodyBones.RightIndexIntermediate, 104 | HumanBodyBones.RightIndexDistal, 105 | HumanBodyBones.RightMiddleProximal, 106 | HumanBodyBones.RightMiddleIntermediate, 107 | HumanBodyBones.RightMiddleDistal, 108 | HumanBodyBones.RightRingProximal, 109 | HumanBodyBones.RightRingIntermediate, 110 | HumanBodyBones.RightRingDistal, 111 | HumanBodyBones.RightLittleProximal, 112 | HumanBodyBones.RightLittleIntermediate, 113 | HumanBodyBones.RightLittleDistal } }, 114 | }; 115 | #endregion 116 | 117 | Transform[] CollectTransforms() { 118 | HashSet xforms = new HashSet(); 119 | 120 | if (AvatarMask != null) { 121 | for (int i = 0; i < AvatarMask.transformCount; ++i) { 122 | string path = AvatarMask.GetTransformPath(i); 123 | var xform = transform.Find(path); 124 | if (xform != null) 125 | xforms.Add(xform); 126 | } 127 | } 128 | 129 | for (int i = 0; i < (int)AvatarMaskBodyPart.LastBodyPart; ++i) { 130 | bool active = AvatarMask == null ? true : AvatarMask.GetHumanoidBodyPartActive((AvatarMaskBodyPart)i); 131 | 132 | HumanBodyBones[] hbbones; 133 | if (!AvatarBodyPartMapping.TryGetValue((AvatarMaskBodyPart)i, out hbbones)) 134 | continue; 135 | foreach (var hbb in hbbones) { 136 | var xform = Animator.GetBoneTransform(hbb); 137 | if (xform == null) 138 | continue; 139 | 140 | if (active) 141 | xforms.Add(xform); 142 | else 143 | xforms.Remove(xform); 144 | } 145 | } 146 | 147 | xforms.Remove(transform); 148 | 149 | return Sym.TransformUtil.SortParentToChild(xforms); 150 | } 151 | 152 | 153 | class InertiaState 154 | { 155 | public Transform[] Transforms; 156 | public TransformState[] States; 157 | 158 | public float CurrTime; 159 | public float DeltaTime; 160 | 161 | public void Trigger(float blendTime) { 162 | for (int i = 0; i < Transforms.Length; ++i) { 163 | Trigger(i, EvaluateSpace.Local, blendTime); 164 | } 165 | } 166 | 167 | public void Trigger(int index, EvaluateSpace space, float blendTime) { 168 | States[index].BeginTime = Time.time; 169 | States[index].EndTime = States[index].BeginTime + blendTime; 170 | States[index].Space = EvaluateSpace.Local; 171 | } 172 | 173 | public void Update() { 174 | float dt = max(1 / 120.0f, DeltaTime); 175 | 176 | for (int i = 0; i < Transforms.Length; ++i) { 177 | TransformState state = States[i]; 178 | 179 | float3 targetPos = Transforms[i].localPosition; 180 | quaternion targetRot = Transforms[i].localRotation; 181 | 182 | 183 | float tf = max(0.0001f, state.EndTime - CurrTime); 184 | float3 pos = InertializeMagnitude(state.PrevPosition, state.Position, targetPos, dt, tf, dt); 185 | quaternion rot = InertializeMagnitude(state.PrevRotation, state.Rotation, targetRot, dt, tf, dt); 186 | 187 | 188 | state.PrevPosition = state.Position; 189 | state.PrevRotation = state.Rotation; 190 | state.Position = pos; 191 | state.Rotation = rot; 192 | 193 | States[i] = state; 194 | 195 | Transforms[i].localPosition = pos; 196 | Transforms[i].localRotation = rot; 197 | } 198 | } 199 | } 200 | 201 | InertiaState PostInertia = null; 202 | 203 | void InitializePostProcess() { 204 | PostInertia = new InertiaState(); 205 | PostInertia.Transforms = CollectTransforms(); 206 | 207 | PostInertia.States = (from pt 208 | in PostInertia.Transforms 209 | select new TransformState 210 | { 211 | Position = pt.localPosition, 212 | PrevPosition = pt.localPosition, 213 | Rotation = pt.localRotation, 214 | PrevRotation = pt.localRotation 215 | }).ToArray(); 216 | 217 | 218 | 219 | 220 | } 221 | 222 | private void LateUpdate() 223 | { 224 | if (PostInertia == null) 225 | InitializePostProcess(); 226 | 227 | if (PostInertia != null) { 228 | PostInertia.CurrTime = Time.time; 229 | PostInertia.DeltaTime = Time.deltaTime; 230 | PostInertia.Update(); 231 | } 232 | } 233 | 234 | public void Trigger(float blendTime){ 235 | PostInertia?.Trigger(blendTime); 236 | } 237 | 238 | } 239 | 240 | 241 | } 242 | -------------------------------------------------------------------------------- /PostInertializerTransitionProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using Unity.Collections; 6 | using Unity.Mathematics; 7 | using static Unity.Mathematics.math; 8 | using static Pd.InertiaAlgorithm; 9 | 10 | namespace Pd 11 | { 12 | public class PostInertializerTransitionProfile : ScriptableObject 13 | { 14 | public struct TransformConfig { 15 | public PostInertializer.EvaluateSpace Space; 16 | public float BlendTime; 17 | } 18 | 19 | public AvatarMask AvatarMask; 20 | 21 | public TransformConfig[] TransformConfigs; 22 | 23 | private int[] IndexMapping; 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inertialization For Unity 2 | 3 | Simple implementation of "Inertialization" algorithm in [High Performance Animation in Gears of War 4](https://cdn.gearsofwar.com/thecoalition/publications/SIGGRAPH%202017%20-%20High%20Performance%20Animation%20in%20Gears%20ofWar%204%20-%20Supplemental.pdf) 4 | 5 | 6 | ## Usage: 7 | * Interpolate quaternion or vector by calling Pd.InertiaAlgorithm.InertializeXXX directly 8 | * Or use PostInertializer behaviour. Specify an AvatarMask, and call PostInertializer.Trigger(\) to smooth out the transition 9 | 10 | --------------------------------------------------------------------------------