Rotating an object around two axes

I’m sure that I’m not the only person who’s had an issue trying to understand rotations in 3D space - I’m trying to figure out why an object won’t rotate around two axes at the same time, and I’m going a bit mad in the process.

I have a Sun in my game, that I’ve implemented by attaching a directional light and a lense flare onto a game object. It works very nicely, and I’ve put a lot of work into a day / night cycle, clouds, skybox, a moon with phases etc. The sun object rotates around the X axis throughout the course of the day, rotating the light and lense flare to mimic the sun moving across the sky.

Now I’m trying to add a bit more detail to the sun; I want to change the height of the sun (and the direction of the light) depending on the time of year. I’m using two dates to determine the summer and winter solstices, and moving the sun between 0 and 90 degrees on the Y axis, to make it look like it’s high and low in the sky. The degrees are configurable. So, there are two different axes rotating at the same time.

The issue I’m facing is this: I can rotate around the X axis and Y axis individually, but as soon as I try to rotate them both together, the Y axis rotation is ignored entirely. I have tried a number of different methods, but I either get spurious results, or nothing at all. Here’s a snippet of code that is called within my Update() function (there are a number of global variables that would take a while to explain, but you should get the general idea):

protected void UpdateSunRotation() {
	// Work out solstice values
	_summerSolsticeDate = new DateTime(CurrentDate.Year, SummerSolsticeMonth, SolsticeDay);
	_winterSolsticeDate = new DateTime(CurrentDate.Year, WinterSolsticeMonth, SolsticeDay);

	var timeBetweenSolstices = _winterSolsticeDate - _summerSolsticeDate;
	_solsticeCycleSeconds = (float)(_dayCycleInSeconds * timeBetweenSolstices.TotalDays);
	
	var solsticeDegreesPerSecond = (WinterSolsticeIncline - SummerSolsticeIncline) / _solsticeCycleSeconds;
	if(CurrentDate >= _winterSolsticeDate || (CurrentDate <= _winterSolsticeDate && CurrentDate <= _summerSolsticeDate)) {
		Debug.Log("Between winter and summer - sun is moving up in the sky");
		solsticeDegreesPerSecond *= -1;
	} else {
		Debug.Log("Between summer and winter - sun is moving down in the sky");
	}

	var sunRotation = SunDegreesPerSecond * SecondsInDay / _dayCycleInSeconds;
	Sun.Rotate(new Vector3(sunRotation, solsticeDegreesPerSecond, 0) * Time.deltaTime, Space.World);
	//Sun.Rotate(new Vector3(sunRotation, 0, 0) * Time.deltaTime, Space.World);
	//Sun.Rotate(new Vector3(0, solsticeDegreesPerSecond, 0) * Time.deltaTime, Space.World);
}

The final uncommented line does not work correctly - only the sunRotation is applied. However, the two commented lines work individually, but have the same effect as the uncommented Sun.Rotate() line if they are both uncommented at once.

I’ve been searching the forums, UA and Google, but I’m yet to find a decent solution for it. I’ve read something about the Gimbal Lock, which I have to admit confuses me even more, but I have a feeling I might be on the right lines… however, I’m sure there are better programmers than me out there that can point me in the right direction…

Can anyone help? It’s melting my brain!

Finally got this working thanks to panbake!

private float currentRotationSunX;
private float currentRotationSunY;

protected void UpdateSunRotation() {
	// Work out solstice values
	_summerSolsticeDate = new DateTime(CurrentDate.Year, SummerSolsticeMonth, SolsticeDay);
	_winterSolsticeDate = new DateTime(CurrentDate.Year, WinterSolsticeMonth, SolsticeDay);
	//Debug.Log("Summer: " + _summerSolsticeDate + " Winter: " + _winterSolsticeDate);
	TimeSpan timeBetweenSolstices;
	float solsticeDegreesPerSecond;
	float solsticeCycleSeconds;

	if(CurrentDate >= _winterSolsticeDate || (CurrentDate <= _winterSolsticeDate && CurrentDate <= _summerSolsticeDate)) {
		Debug.Log("Between winter and summer - sun is moving up in the sky");
		// Set the summer solstice date to NEXT year
		_summerSolsticeDate = _summerSolsticeDate.AddYears(1);
		// Calculate the winter -> summer time period
		timeBetweenSolstices = _summerSolsticeDate - _winterSolsticeDate;

		// Calculate how many realtime seconds it takes to reach a solstice
		solsticeCycleSeconds = (float)(_dayCycleInSeconds * timeBetweenSolstices.TotalDays);
		solsticeDegreesPerSecond = ((SummerSolsticeIncline - WinterSolsticeIncline) / solsticeCycleSeconds);
	} else {
		Debug.Log("Between summer and winter - sun is moving down in the sky");
		// Solstice dates are from this year
		timeBetweenSolstices = _winterSolsticeDate - _summerSolsticeDate;
		// Calculate how many realtime seconds it takes to reach a solstice
		solsticeCycleSeconds = (float)(_dayCycleInSeconds * timeBetweenSolstices.TotalDays);
		solsticeDegreesPerSecond = ((WinterSolsticeIncline - SummerSolsticeIncline) / solsticeCycleSeconds);
	}

	Debug.Log("timeBetweenSolstices: " + timeBetweenSolstices);
	Debug.Log("_solsticeCycleSeconds:" + solsticeCycleSeconds);
	Debug.Log("solsticeDegreesPerSecond: " + solsticeDegreesPerSecond);

	// Calculate the sun's rotation value
	var sunDegreesPerSecond = DayDegreesPerSecond * SecondsInDay / _dayCycleInSeconds;

	//Sun.Rotate(new Vector3(sunDegreesPerSecond, 0, 0) * Time.deltaTime);
	//Sun.Rotate(new Vector3(0, solsticeDegreesPerSecond, 0) * Time.deltaTime);

	currentRotationSunX += sunDegreesPerSecond*Time.deltaTime;
	currentRotationSunY += solsticeDegreesPerSecond * Time.deltaTime;

	var qX = Quaternion.AngleAxis(currentRotationSunX, Vector3.right);
	var qY = Quaternion.AngleAxis(currentRotationSunY, Vector3.up);
	var q = qX * qY;
	//Rotates about the local axis
	Sun.localRotation = q;
}

The code needs some serious refactoring and performance improvements, and it doesn’t quite work as I want, but the issue I was facing has been sorted! Thank you panbake :smiley: