Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Mono vs DOTS quaternion issues

Discussion in 'Entity Component System' started by Sovogal, Aug 23, 2023.

  1. Sovogal

    Sovogal

    Joined:
    Oct 15, 2016
    Posts:
    98
    Hey all. I am having an issue rotating my camera in ECS. I've created a pretty simple piece of code to demonstrate the issue. The code that executes in MonoBehaviour form works as expected. However, the code that executes in ECS seems to produce a gimbal lock. I have tried all variations of quaternion.Euler, but the results remain the same.
    Code (CSharp):
    1. using Unity.Entities;
    2. using UnityEngine;
    3. using Unity.Burst;
    4. using Unity.Collections;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7.  
    8. namespace Assets.Scripts.Test
    9. {
    10.     public class CameraRotation : MonoBehaviour
    11.     {
    12.         public float Speed = 1f;
    13.  
    14.         public Vector3 TargetEuler = new Vector3(30f, 180f, 0f);
    15.  
    16.         public void Update()
    17.         {
    18.             var cameraEuler = this.transform.rotation.eulerAngles;
    19.             var interpolatedEuler = Vector3.Lerp(cameraEuler, TargetEuler, Time.deltaTime * Speed);
    20.             var offsetEuler = interpolatedEuler - cameraEuler;
    21.             var rotationInfluence = Quaternion.Euler(offsetEuler);
    22.             this.transform.rotation = Quaternion.LookRotation(this.transform.rotation * rotationInfluence * Vector3.forward, Vector3.up);
    23.         }
    24.     }
    25.  
    26.     public class CameraRotationBaker : Baker<CameraRotation>
    27.     {
    28.         public override void Bake(CameraRotation authoring)
    29.         {
    30.             this.AddComponent(GetEntity(authoring, TransformUsageFlags.Dynamic), new RotateCameraData
    31.             {
    32.                 Speed = authoring.Speed,
    33.                 TargetEuler = math.radians(authoring.TargetEuler)
    34.             });
    35.         }
    36.     }
    37.  
    38.     public struct RotateCameraData : IComponentData
    39.     {
    40.         public float Speed;
    41.         public float3 TargetEuler;
    42.     }
    43.  
    44.     [UpdateInGroup(typeof(SimulationSystemGroup))]
    45.     [BurstCompile]
    46.     public partial struct CameraBehaviorSystem : ISystem
    47.     {
    48.         private EntityQuery _query;
    49.  
    50.         public void OnCreate(ref SystemState state)
    51.         {
    52.             using (var builder = new EntityQueryBuilder(Allocator.Temp)
    53.                        .WithAllRW<RotateCameraData>()
    54.                        .WithAllRW<LocalTransform>())
    55.             {
    56.                 _query = builder.Build(ref state);
    57.             }
    58.         }
    59.      
    60.         [BurstCompile]
    61.         public void OnUpdate(ref SystemState state)
    62.         {
    63.  
    64.             CameraUpdateJob cameraUpdateJob = new CameraUpdateJob
    65.             {
    66.                 DeltaTime = state.WorldUnmanaged.Time.DeltaTime,
    67.             };
    68.             state.Dependency = cameraUpdateJob.Schedule(_query, state.Dependency);
    69.         }
    70.     }
    71.  
    72.     [BurstCompile]
    73.     public partial struct CameraUpdateJob : IJobEntity
    74.     {
    75.         public float DeltaTime;
    76.      
    77.         public void Execute(ref LocalTransform camera, ref RotateCameraData rotateCameraData)
    78.         {
    79.             // With eulers
    80.             // camera.Rotation.eulerAngles(out float3 cameraEuler);
    81.             // var interpolatedEuler = math.lerp(cameraEuler, rotateCameraData.TargetEuler, DeltaTime * rotateCameraData.Speed);
    82.             // var offsetEuler = interpolatedEuler - cameraEuler;
    83.             // var rotationInfluence = quaternion.Euler(offsetEuler);
    84.          
    85.             //With mostly quaternions
    86.             var interpolatedRotation = math.slerp(camera.Rotation, quaternion.Euler(rotateCameraData.TargetEuler), DeltaTime * rotateCameraData.Speed);
    87.             var offsetRotation = math.mul(interpolatedRotation, math.inverse(camera.Rotation));
    88.             var rotationInfluence = offsetRotation;
    89.          
    90.          
    91.             camera.Rotation = quaternion.LookRotationSafe(math.mul(math.mul(camera.Rotation, rotationInfluence), math.forward()), math.up());
    92.         }
    93.     }
    94. }
    The commented-out code was my initial attempt. The uncommented code below that is me trying it while minimizing the use of eulers. Anyone run into this issue or know how I can go about fixing it?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    The easiest solution is avoid conversion of Quaternions into quaternions and vice versa.
    If you need Quaternions - use Mathf. If you need quaternions - use math.
    Both Quaternions and quaternion can be used in jobs and even burst compiled since they're structs.
    For something like camera (which is usually low quantity entity), it shouldn't really matter performance wise.

    Proper solution is to avoid eulers after authoring phase.
    Use directions to figure out what and where should point. Its easier than figuring out angles.
    Or alternatively, "add" quaternions by storing initial quaternion / rotation, and a separate angle per axis.
     
    Arnold_2013 likes this.
  3. Sovogal

    Sovogal

    Joined:
    Oct 15, 2016
    Posts:
    98
    That's definitely not what's going on in the code. However, you did inspire me to convert the euler to a quaternion at bake time instead of at runtime. Still getting a gimbal lock. Must be related to LookRotation somehow.
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Gimbal locks are caused by using angles instead of quaternions.
    Its just how mathematics works. LookRotation works a bit differently for the math lib as well.

    For example, if passed forward direction matches passed up axis - you'll get a completely messed up rotation.
    Where as non math-lib variant would work just fine (as it is caught instead of being silently processed).