protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (m_MouseGroup.CalculateEntityCount() == 0)
            {
                return(inputDeps);
            }

            ComponentDataFromEntity <Translation>     Positions  = GetComponentDataFromEntity <Translation>(true);
            ComponentDataFromEntity <Rotation>        Rotations  = GetComponentDataFromEntity <Rotation>(true);
            ComponentDataFromEntity <PhysicsVelocity> Velocities = GetComponentDataFromEntity <PhysicsVelocity>();
            ComponentDataFromEntity <PhysicsMass>     Masses     = GetComponentDataFromEntity <PhysicsMass>(true);

            // If there's a pick job, wait for it to finish
            if (m_PickSystem.PickJobHandle != null)
            {
                JobHandle.CombineDependencies(inputDeps, m_PickSystem.PickJobHandle.Value).Complete();
            }

            // If there's a picked entity, drag it
            MousePickSystem.SpringData springData = m_PickSystem.SpringDatas[0];
            if (springData.Dragging != 0)
            {
                Entity entity = m_PickSystem.SpringDatas[0].Entity;
                if (!EntityManager.HasComponent <PhysicsMass>(entity))
                {
                    return(inputDeps);
                }

                PhysicsMass     massComponent     = Masses[entity];
                PhysicsVelocity velocityComponent = Velocities[entity];

                if (massComponent.InverseMass == 0)
                {
                    return(inputDeps);
                }

                var worldFromBody = new MTransform(Rotations[entity].Value, Positions[entity].Value);

                // Body to motion transform
                var        bodyFromMotion  = new MTransform(Masses[entity].InertiaOrientation, Masses[entity].CenterOfMass);
                MTransform worldFromMotion = Mul(worldFromBody, bodyFromMotion);

                // Damp the current velocity
                const float gain = 0.95f;
                velocityComponent.Linear  *= gain;
                velocityComponent.Angular *= gain;

                // Get the body and mouse points in world space
                float3 pointBodyWs   = Mul(worldFromBody, springData.PointOnBody);
                float3 pointSpringWs = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, springData.MouseDepth));

                // Calculate the required change in velocity
                float3 pointBodyLs = Mul(Inverse(bodyFromMotion), springData.PointOnBody);
                float3 deltaVelocity;
                {
                    float3 pointDiff = pointBodyWs - pointSpringWs;
                    float3 relativeVelocityInWorld = velocityComponent.Linear + math.mul(worldFromMotion.Rotation, math.cross(velocityComponent.Angular, pointBodyLs));

                    const float elasticity = 0.1f;
                    const float damping    = 0.5f;
                    deltaVelocity = -pointDiff * (elasticity / Time.fixedDeltaTime) - damping * relativeVelocityInWorld;
                }

                // Build effective mass matrix in world space
                // TODO how are bodies with inf inertia and finite mass represented
                // TODO the aggressive damping is hiding something wrong in this code if dragging non-uniform shapes
                float3x3 effectiveMassMatrix;
                {
                    float3 arm  = pointBodyWs - worldFromMotion.Translation;
                    var    skew = new float3x3(
                        new float3(0.0f, arm.z, -arm.y),
                        new float3(-arm.z, 0.0f, arm.x),
                        new float3(arm.y, -arm.x, 0.0f)
                        );

                    // world space inertia = worldFromMotion * inertiaInMotionSpace * motionFromWorld
                    var invInertiaWs = new float3x3(
                        massComponent.InverseInertia.x * worldFromMotion.Rotation.c0,
                        massComponent.InverseInertia.y * worldFromMotion.Rotation.c1,
                        massComponent.InverseInertia.z * worldFromMotion.Rotation.c2
                        );
                    invInertiaWs = math.mul(invInertiaWs, math.transpose(worldFromMotion.Rotation));

                    float3x3 invEffMassMatrix = math.mul(math.mul(skew, invInertiaWs), skew);
                    invEffMassMatrix.c0 = new float3(massComponent.InverseMass, 0.0f, 0.0f) - invEffMassMatrix.c0;
                    invEffMassMatrix.c1 = new float3(0.0f, massComponent.InverseMass, 0.0f) - invEffMassMatrix.c1;
                    invEffMassMatrix.c2 = new float3(0.0f, 0.0f, massComponent.InverseMass) - invEffMassMatrix.c2;

                    effectiveMassMatrix = math.inverse(invEffMassMatrix);
                }

                // Calculate impulse to cause the desired change in velocity
                float3 impulse = math.mul(effectiveMassMatrix, deltaVelocity);

                // Clip the impulse
                const float maxAcceleration = 250.0f;
                float       maxImpulse      = math.rcp(massComponent.InverseMass) * Time.fixedDeltaTime * maxAcceleration;
                impulse *= math.min(1.0f, math.sqrt((maxImpulse * maxImpulse) / math.lengthsq(impulse)));

                // Apply the impulse
                {
                    velocityComponent.Linear += impulse * massComponent.InverseMass;

                    float3 impulseLs        = math.mul(math.transpose(worldFromMotion.Rotation), impulse);
                    float3 angularImpulseLs = math.cross(pointBodyLs, impulseLs);
                    velocityComponent.Angular += angularImpulseLs * massComponent.InverseInertia;
                }

                // Write back velocity
                Velocities[entity] = velocityComponent;
            }

            return(inputDeps);
        }
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (m_MouseGroup.CalculateLength() == 0)
            {
                return(inputDeps);
            }

            var Positions = GetComponentDataFromEntity <Translation>();

            // If there's a pick job, wait for it to finish
            if (m_PickSystem.PickJobHandle != null)
            {
                JobHandle.CombineDependencies(inputDeps, m_PickSystem.PickJobHandle.Value).Complete();
            }

            // If there's a picked entity, drag it
            MousePickSystem.SpringData springData = m_PickSystem.SpringDatas[0];
            var physicsWorld = m_PickSystem.m_BuildPhysicsWorldSystem.PhysicsWorld;

            if (springData.Dragging != 0)
            {
                Entity entity = m_PickSystem.SpringDatas[0].Entity;

                if (!m_WasDragging)
                {
                    GameMgr.g.PlayPickMonsterSoundEffect(entity);
                }

                m_WasDragging = true;
                Translation posComponent = Positions[entity];
                m_SelectedEntity = entity;

                var hits = new NativeList <RaycastHit>(Allocator.TempJob);
                physicsWorld.CollisionWorld.CastRay(MousePickBehaviour.CreateRayCastFromMouse(), ref hits);
                var terrainEntities = m_TerrainEntityQuery.ToEntityArray(Allocator.TempJob);
                foreach (var terrainEntity in terrainEntities)
                {
                    var index = physicsWorld.GetRigidBodyIndex(terrainEntity);
                    foreach (var hit in hits.ToArray())
                    {
                        if (hit.RigidBodyIndex == index)
                        {
                            posComponent.Value.x = hit.Position.x;
                            posComponent.Value.z = hit.Position.z;
                            Positions[entity]    = posComponent;
                            break;
                        }
                    }
                }

                hits.Dispose();
                terrainEntities.Dispose();
            }
            else if (m_WasDragging)
            {
                m_WasDragging = false;
                var hits = new NativeList <RaycastHit>(Allocator.TempJob);
                physicsWorld.CollisionWorld.CastRay(MousePickBehaviour.CreateRayCastFromMouse(), ref hits);

                var roomEntities = m_RoomEntityQuery.ToEntityArray(Allocator.TempJob);
                foreach (var room in roomEntities)
                {
                    var index = physicsWorld.GetRigidBodyIndex(room);
                    foreach (var hit in hits.ToArray())
                    {
                        if (hit.RigidBodyIndex == index)
                        {
                            GameMgr.MoveMonsterToRoom(m_SelectedEntity, room, 0);
                        }
                    }
                }

                hits.Dispose();
                roomEntities.Dispose();
            }

            return(inputDeps);
        }