Allign object y axis with normal

Godot Version

4.3

Question

how would I go about aligning an object’s y axis witha given normal through code? I’m completely lost in terms of rotation math

This is the code I’m working with rn:

func align_with_normal(object_to_rotate: Node3D, normal: Vector3):
	var b = Basis()
	var forward = basis.y
	b = b.rotated(basis.x, forward.angle_to(normal))
	object_to_rotate.transform = Transform3D(basis, object_to_rotate.transform.origin)

not certain why it’s not doing what I want as of right now

Here’s my hacky way that should achieve what you want.

func align_with_normal(object_to_rotate: Node3D, normal: Vector3):
	object_to_rotate.global_transform = object_to_rotate.global_transform.looking_at(object_to_rotate.global_position + normal)
	object_to_rotate.global_transform = object_to_rotate.global_transform.rotated(object_to_rotate.global_basis * Vector3.LEFT, PI / 2)

In the first line, we’re using looking_at() to rotate the Z axis towards the normal.
In the second line, we’re rotating the object 90 degrees forward to have it face Y axis towards the normal.

There has to be a better and cleaner way to do it, but that’s what first came to my mind and I don’t see any better way myself.

hey, thanks a ton. it seems to be teleporting the object a pretty significant distance though, any idea why that might be?

Ah, I didn’t test it outside of the origin position. Moving outside of origin indeed teleports it around.
Here’s ChatGPT’s solution, that seems to work better, if you don’t mind the LLM:

func align_with_normal(object_to_rotate: Node3D, normal: Vector3):
	var y = normal.normalized()
	var x = y.cross(Vector3.FORWARD).normalized()
	if x.length() == 0:
		x = y.cross(Vector3.RIGHT).normalized()
	var z = x.cross(y).normalized()
	object_to_rotate.global_transform = Transform3D(Basis(x, y, z), object_to_rotate.global_position)

it seems like it’s putting the y axis perpendicular to the normal rather than parallel, is there something I might be missing?

You can use the arc from → to quaternion constructor

object_to_rotate.quaternion = Quaternion(Vector3.UP, normal)
1 Like

this seems way closer, but it’s placing the object at an angle to the normal. to clarify: the object’s y axis should be parallel to the normal. this seems to be part of the solution but not quite the whole

This should be parallel to the normal, can you screenshot what you are seeing and what you expect?

in the first screenshot here, the Red Cross in the center of the screen is the intersection of the dome and cylinder parts of a capsule collider(intended object to rotate), representing the player. a raycast is fired from the middle of the view, selecting a face(outlined in yellow)

in the second screenshot you can see the resulting rotation of the capsule collider . the outlined face is the initially selected face. as indicated by the camera orientation, the capsule collider is not parallel with the normal of this face

this final screenshot shows the “bottom” of the capsule collider in relation to the yellow outline.

essentially I’d like to take the normal of the selected face, align the player with it, and treat that as the gravity direction for the player, like magnet boots in space.

here is my current implementation of this idea:

func detect_magnetized():
	if Input.is_action_just_pressed("Magnetize"):
		if !magnetized:#check if you are currently magnetized or free floating
			if eyes_cast.get_collider()
				grav_normal = eyes_cast.get_collision_normal()#sample normal of colllision point of raycast
				self.quaternion = Quaternion(Vector3.UP, grav_normal)#given code, thank you for your help btw
				axis_lock_angular_x = true
				axis_lock_angular_y = true
				axis_lock_angular_z = true
				magnetized = true
		else:
			magnetized = false
			body.rotation = camera.global_rotation
			head.rotation = Vector3.ZERO
			camera.rotation = Vector3.ZERO

Sadly I’m not getting it, maybe your expecting the camera to have rotated too, but you are only setting the self.quaternion which I presume is the player (CharacterBody3D?) and the camera is a child or grand-child of it so the cameras rotation will be offset because it inherits from the parents. Maybe reset the camera and it’s parent’s rotation during this operation?

self.quaternion = Quaternion(Vector3.UP, grav_normal)
camera.rotation = Vector3.ZERO
head.rotation = Vector3.ZERO

I ended up just rotating the collider 90 degrees and using “lookat” with the parent rigidbody so it would orient the colider correctly.

fyi I pulled this from another discussion, lost the link

var old = transform.basis
	look_at(global_position + grav_normal, Vector3.UP) 
	var new = transform.basis
	transform.basis = lerp(old, new, .1)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.