2

I am trying to rotate the light source of my Scene on one Axis with this code:

IEnumerator SunMoving()
{
    while (true)
    {
        Quaternion fromAngle = transform.rotation;
        Quaternion toAngle = Quaternion.Euler(transform.eulerAngles + (Vector3.left * dayRotation));

        for(float t = 0f;  t < 1; t += Time.deltaTime)
        {
            transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t);
            yield return null;
        }
    }
}

void Start()
{
    dayRotation = 360 / daySeconds;
    //nightRotation = 180 / nightSeconds;
    StartCoroutine(SunMoving());
}

But it is not working correctly. I am really new to quaternions and rotation in general, so any help is appreciated.

Thanks in advance and sorry for my bad english :)

2
  • But it is not working correctly. what exactly does this mean? Commented Aug 22, 2022 at 13:03
  • Sorry for not clarifying, the object beeing rotated would sometimes just stop at a point an get stuck there rotating back and forth. I actually just could not figure out, how this outcome would be possilbe. I will have to further look into quaternions and rotation to understand :) Commented Aug 22, 2022 at 17:15

3 Answers 3

4

If you want to animate a sun object (directional light) you don't lerp between two different rotations but calculate angles from a formula that approximates this continuous motion

sun light controller

// src* https://gist.github.com/andrew-raphael-lukasik/2f337c0e6c467f084cded9bf63dd8556
using UnityEngine;

public class SunController : MonoBehaviour
{
    [SerializeField][Range(0,1)] float _dayTime = 0.5f;// 0.5 means noon
    [SerializeField] float _timeScale = 1;
    [SerializeField] Light _sun = null;// make sure it is directional
    [SerializeField] Vector2 _sunAtMidnight = new Vector2(45,0);// degrees
    [SerializeField] Vector2 _earthRotationAxis = new Vector2(-45,0);// degrees, axis relative to your location (so pointing straight up if you are on a N or S pole)
    const float degreesPerSecond = 360f / secondsPerDay;
    const float secondsPerDay = 24*60*60;// seconds per day
    [SerializeField] AnimationCurve _sunIntensity = new AnimationCurve(
        new Keyframe(0,0) ,
        new Keyframe(0.25f,0) ,
        new Keyframe(0.5f,1) ,
        new Keyframe(0.75f,0) ,
        new Keyframe(1,0)
    );
    [SerializeField] Gradient _sunColor = new Gradient{ colorKeys=new GradientColorKey[]{
        new GradientColorKey(new Color(1,0.3f,0,1),0) ,
        new GradientColorKey(Color.white,0.5f) ,
        new GradientColorKey(new Color(1,0.3f,0,1),1)
    } };

    void FixedUpdate ()
    {
        // step simulation time:
        _dayTime = ( _dayTime + ( Time.fixedDeltaTime * _timeScale ) / secondsPerDay ) % 1f;

        // update directional light:
        Vector3 sunPositionNow = SunPositionAtTime(_dayTime);
        _sun.transform.rotation = Quaternion.LookRotation( -sunPositionNow );
        _sun.intensity = _sunIntensity.Evaluate(_dayTime);
        _sun.color = _sunColor.Evaluate(_dayTime);
        _sun.enabled = _sun.intensity>0;
    }

    /// <param name="t">0-1 value range</param>
    Vector3 SunPositionAtTime ( float t )
    {
        Vector3 midnightDir = Quaternion.Euler((Vector3)_sunAtMidnight) * Vector3.forward;
        Vector3 earthAxisDir = Quaternion.Euler((Vector3)_earthRotationAxis) * Vector3.forward;
        return Quaternion.AngleAxis( t*secondsPerDay*degreesPerSecond , earthAxisDir ) * midnightDir;
    }

#if UNITY_EDITOR
    void OnDrawGizmos ()
    {
        Vector3 position = transform.position;
        
        Gizmos.color = Color.cyan * 0.3f;
        Gizmos.DrawRay( position , Quaternion.Euler((Vector3)_earthRotationAxis) * Vector3.forward );
        
        Gizmos.color = Color.black;
        Gizmos.DrawRay( position , Quaternion.Euler((Vector3)_sunAtMidnight) * Vector3.forward );

        if( Application.isPlaying )
        {
            Gizmos.color = Color.white;
            Gizmos.DrawRay( position , SunPositionAtTime(_dayTime) );
        }

        int numSteps = 100;
        for( int i=0 ; i<=numSteps ; i++ )
        {
            float t = (float)i / (float)numSteps;
            Gizmos.color = _sunColor.Evaluate(t) * new Color(1,1,1,Mathf.Max(_sunIntensity.Evaluate(t),0.05f));
            Gizmos.DrawRay( position , SunPositionAtTime(t) );
        }

        Gizmos.color = Color.yellow * 0.3f;
        Gizmos.DrawWireSphere( position , 1f );
        
        int dayTimeSeconds = (int)(_dayTime * secondsPerDay);
        int h = dayTimeSeconds/(60*60);
        int m = (dayTimeSeconds%(60*60))/60;
        int s = dayTimeSeconds%60;
        UnityEditor.Handles.color = Color.red;
        UnityEditor.Handles.Label( position , $"time of day: {h:00.}:{m:00.}:{s:00.}" );
    }
#endif

}
Sign up to request clarification or add additional context in comments.

Comments

3

In general do not use eulerAngles unless you know how they work:

When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.

When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation. See bottom scripting example for more information.

To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.

rather calculate your target rotation as

Quaternion fromAngle = transform.rotation;
Quaternion toAngle = fromAngle * Quaternion.Euler(Vector3.left * dayRotation);

Further using Lerp in your use case of an endless continuous linear rotation seems a bit unnecessary.

You could without dealing with the Quaternions at all also simply Rotate by a fix angle:

[SerializeFied]
private float daySeconds = 1f;

private void Update()
{
    transform.Rotate(-360f / daySeconds * Time.deltTime, 0, 0);
}

Comments

-1

You should use the update function instead of the start function to rotate stuff. Secondly quaternions have an extra dimension w which needs to be computed. A difficult aspect of quaternions is that the order of operation matters a lot. -> transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t); not sure about this line.

2 Comments

Hi Andrei, please allow me to clear few things out ok? "You should use the update function instead of the start function to rotate stuff" He is using Update but indirectly via a Coroutine "Secondly quaternions have an extra dimension w which needs to be computed" Quaternion.Euler is a handy method that computes all these quaternion components for you
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.