Camera clamp not working when the camera is tilted on a Z Axis

Godot Version

4.2.2.Stable

Question

Hey!, so i made this headbob with the help of a fellow forum user but it seems like clamp stops working when it’s rotated

here’s a video showing what i mean:

My headbob code:

#Bob variables
const BOB_FREQ = 2
const BOB_AMP = 0.08
const BOB_AMP_Y = 0.03
const BOB_FREQ_Y = 1
var t_bob = 0.0

@onready var pivot = $Pivot
@onready var camera = $Pivot/Camera3D

func _physics_process(delta):
###
	t_bob += delta * velocity.length() * float(is_on_floor())
	pivot.transform.origin = _headbob(t_bob)

func _headbob(time) -> Vector3:
	var pos = Vector3.ZERO
	pos.y = sin(time * BOB_FREQ) * BOB_AMP
	pos.x = sin(time * BOB_FREQ/2) * BOB_AMP
	camera.rotation.z = sin(t_bob * BOB_FREQ_Y) * BOB_AMP_Y
	return pos

How do i fix this? am i stupid

If you use clamp on a vector be advised that it will clamp all values and not just one. You need to clamp each axis separately.

So if you use the vector 3 clamp

Vec3 = (-1,.5,.5)

Vec3 = Vec3.clamp(vector.zero, vector.one)

Print(vec3)
# (0,0,0) , because it will clamp all axis.

You should clamp like this on a vector

Vec3 = vector3( 
  clamp(vec3.x, 0, 1 ), 
  clamp(vec3.y, ... Etc.

My guess is that there is more likely a chance that the camera is hitting the clamped boundary when your bob is enabled.

1 Like

how exactly do i implement that? i am fairly new so i don’t really know

Can you share your camera rotation code? Or the code that uses clamp? If you use clamp?

here:

@onready var pivot = $Pivot
@onready var camera = $Pivot/Camera3D

#Mouse
func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _unhandled_input(event):
	if event is InputEventMouseMotion:
		pivot.rotate_y(-event.relative.x * SENSITIVITY)
		camera.rotate_x(-event.relative.y * SENSITIVITY)
		camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-85), deg_to_rad(85))

So you aren’t using clamp in the way I described, but there probably is some subtle math in here… Give me a sec

1 Like

[ edit: this is not correct statement about rotation values, the range is -pi to pi ]

I’m wondering about these function calls. Typically rotation is in radians from 0 to 2π(for 0° to 360°). There is no negative radian values. So I’m wondering if this is actually working as expected.

The other thing is that you change the rotation first and then clamp. It could be possible that the relative movement of the mouse is high enough that you could shoot past the clamp zone.

In the past I have used basis vectors y and z to determine the camera limits.

how is it supposed to look like? i’m curious

One sec I need to get to my computer.

I guess another other thing is that since you are rotating on the z axis for bobing your camera may be gimbal locking causing slight extra rotation. But probably not that big of a deal because you have the pivot node.

I think one solution to that is adding another node between the pivot and camera.

Okay, thank you so much for helping me, please tell me when you have a fix :smiley_cat:

func look(rel_mouse_pos:Vector2):
	#rotate x axis up and down
	rotate_object_local(Vector3.LEFT,rel_mouse_pos.y * speed)

	#clamp rotation if y basis is pointing below the horizon
	if transform.basis.y.y < 0.0:
        # z basis tells us if we are pointed up or down
		self.rotation.x = -PI/2.0 * sign(transform.basis.z.y) 

	# y rotation for left right
	rotate_y(-rel_mouse_pos.x * speed)

This isnt a fix, but just an example of of using basis vectors for rotation checks.

I think if you used rotate_object_local you could avoid the gimbal lock sitation as this would ignore the z axis bob.

if you wanted to add a limit similar to 85 degrees you can replace PI/2.0 (90 degrees) with your limit.

is that a replacement for my clamp code? if so, how do i implement it? and does that fix it or atleast makes thing any easier

since you say it works with the bob disabled, I would try the rotate_object_local function. in your unhanded input like so

camera.rotate_object_local(Vector3.LEFT, -event.relative.y * SENSITIVITY)

Try this first

(I made some claims about rotations, it seems like they can go negative, and it is split on the simi-circle of pi; the range is ( -pi, pi ) and then it rolls over. so the clamp code is okay)

1 Like

That seems to make things a bit better, however, if i force my mouse up or down, it glitches and even spins while mousing straight up, and the controls become a buggy mess, directions randomize everytime you aim up… but atleast the headbob doesnt glitch the camera clamp anymore, yay

So I tried pulling in your code, and I noticed an issue with the clamp behavior.

Its kind of weird, basically the x axis rotation’s range is only from -pi/2 to pi/2, and you have a small area to clamp (5 degrees ). It is very easy to move past the clamp region with a high sensitivity. and this happens for me regardless if the bob is active. I think if you moved to the basis-vector based clamp it should solve that issue. at least make it harder since it would require a mouse movement of a whole (+180 degrees).

I would replaice this code

camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-85), deg_to_rad(85))

with this

if camera.transform.basis.y.y < 0.0:
	camera.rotation.x = -deg_to_rad(85) * sign(camera.transform.basis.z.y) 

that didn’t work out either, damn this is difficult.
now it still makes that flicking and headbob glitches it too

sorry i made a mistake it should be this above

the 0.08715574 is basically sin(deg_to_rad(5)), for 5 degrees.

any way it seems to work for me…

is your code like this?

func _unhandled_input(event):
	if event is InputEventMouseMotion:
		pivot.rotate_y(-event.relative.x * SENSITIVITY)
		camera.rotate_object_local(Vector3.LEFT, -event.relative.y * SENSITIVITY)
		if camera.transform.basis.y.y < 0.08715574:
	         camera.rotation.x = -deg_to_rad(85) * sign(camera.transform.basis.z.y) 

I can give you the project on github, maybe it’s an issue on my end

1 Like

sure, lets do it.

Here:

please let me know if it works as i never used github before