How to rotate 3D objects to always face up

Godot Version

4.6

Question

I’m trying to simulate the growth of a tree. The branches can grow at various angles, but I want the leaves to always face up, towards the light. If I understand 3d transforms correctly, all I need to do is to align the object’s basis to global XYZ axes, so I put something like this in the _ready function:

func _ready() -> void:
	var initial_rotation_y = global_rotation.y
	global_transform.basis = Basis(Vector3.RIGHT, Vector3.UP, Vector3.FORWARD)
	scale = Vector3(0.1, 0.1, 0.1)
	global_rotation.y = initial_rotation_y

Unfortunately I got something like this:

What did I get wrong? I expect every leaf to be a line from this perspective, like on the main stem.

Hi! It looks to me the leaves are facing up each branch instead of the stem. Either the code snippet did its job and the new orientation got applied, then just orient them upwards relative to the stem instead of each branch. If the code did not do its job something might have gone wrong along the way.

A common pitfall to me is that each node needs to be added to the scene tree first before using (getting and setting) the global orientation.

Use Node3D:global_rotate(axis, angle).

You want to do the smallest angle rotation that aligns the current leaf normal vector to global up vector:

  • transform the leaf normal vector to global space using Node3D::to_global() or multiplying by global basis.
  • calculate the axis by taking the cross product between such normal and global up vector
  • calculate the angle between those two vector using dot product or `Vector3::signed_angle_to())
  • rotate by calculated angle around calculate axis using Node3D::global_rotate()
1 Like

How do I get the normal vector? Is it just the y component of object’s basis? Locally the leaf lies flat on the XZ plane.

If the leaf is in xz plane then yes, local normal will be basis.y. Just make sure to normalize it if there’s some y scaling on the leaf.

I think I replicated your instructions correctly:

func _ready() → void:
var normal_vector = to_global(basis.y.normalized())
var up = Vector3.UP
var axis = normal_vector.cross(up)
var angle = normal_vector.signed_angle_to(up, axis)
global_rotate(axis, angle)

But I get this:

I’m starting to question what ‘global’ actually means.

Any errors in the output console?

There was, it wanted me to normalize the calculated axis. Fixing that didn’t really change much unfortunately. I think there is some rotation now, but nowhere near what I expect there to be.

Let’s see the code. Try Vector3.UP instead of basis.y

I got it to work. I used the same code as I posted in the original question, but instead of the leaf’s _ready function I created a function in the script for the parent scene and called it there. Makes me REALLY question what ‘global’ actually means. When I created a separate function in the script for leaf and called it in the script for the parent scene it didn’t work either. ¯\_(ツ)_/¯

Thank you very much for your help. <3

Now I can finally start playing around with camera controls so players don’t just see horizontal lines. :smiley:

EDIT:

Just for clarity’s sake, this is the code:

func _rotate_leaf(leaf: Node3D) -> void:
	var initial_rotation_y = leaf.global_rotation.y
	leaf.global_transform.basis = Basis(Vector3.RIGHT, Vector3.UP, Vector3.FORWARD)
	leaf.scale = Vector3(0.1, 0.1, 0.1)
	leaf.global_rotation.y = initial_rotation_y