PowerUpT Format your code properly using ``` tags

What's the maximal slope? Does the gravity needs to change as well so it points down the slope normal?

    xyz Strange, I clicked the code markdown button and it formatted it incorrectly. I think that needs to be fixed.

    For my purposes, there is no maximal slope. If there is a curved surface that goes upside down, then the player should stick to the ceiling.

    The gravity would need to change, but I was currently just trying to get the player rotations working first. That is pretty important though.

    Thanks

    • xyz replied to this.

      PowerUpT Is there a maximal relative slope? Or the player can transition from a horizontal surface directly to an overhang (more than 90 deg) surface?

      So if I understood correctly, you want every surface to act as a horizontal floor regardless of its absolute incline?

        xyz Oh I see.
        I didn't think of that yet. What I have now is that there's a threshold for not snapping in an absolute sense (0.4 rad for now, so that stuff like stairs and slight differences in the ground don't affect it), but haven't considered a threshold.

        I'm thinking something like 25 degrees.

        • xyz replied to this.

          PowerUpT Ok. When dealing with orientation in space you should avoid using euler rotation angles. There are multiple reasons for that which I won't go into here. Work with quaternions or directly with basis vectors.

          So in order to orient the player node properly you need to construct its orthogonal basis i.e. figure out how the three basis vectors that define its local coordinate space are positioned relative to the global coordinate system.

          In practice you only need two of those vectors as, due to the basis orthogonality constraint, the third one can be calculated via the cross product of the other two (cross product of two vectors is always perpendicular to both of them)

          You already have one of those vectors explicitly defined - the surface normal vector. This is your basis up (or y) vector. The basis forward/back (or z) vector can be defined as the projection of the camera look direction onto the current "floor" plane. This is trivial to calculate from the normal vector and the camera look vector using Plane::project()

          Now you have two orthogonal basis vectors. Take their cross product to get the third vector. Those 3 vectors constitute your final orientation basis that can be assigned directly to the player node.

            xyz I have tried this already, and there is an issue. Is there a way to apply the quaternion without setting the entire transform of the object? I have done this by creating a Transform3D with this data and directly applying it to the player's transform, but the issue is with the script is that it constrains the ability for the player to rotate on the Y axis using the mouse.

            Is there a way around this so that the quaternion won't interfere with mouselook? This is the reason I've been trying to do it with rotations.

            • xyz replied to this.

              PowerUpT Use intermediary node(s). Or first rotate it and then apply the whole transform. If the transform is properly calculated it will retain the rotation.
              And btw the camera shouldn't be parented to a collider. Parent it to a character body or an intermediate Node3D

              PowerUpT Here's proper basis calculation code. It takes floor normal and camera look direction and returns the basis whose y axis is aligned with the normal and -z axis with floor projected look vector:

              func calc_basis(floor_normal: Vector3, camera_look: Vector3) -> Basis:
              	var b = Basis()
              	b.y = floor_normal
              	b.z = Plane(b.y).project(camera_look)
              	b.x = b.y.cross(b.z)
              	return b.orthonormalized()

                xyz Sorry about the late reply, I was at work.

                Thanks for the help so far. I'm not sure if I'm just not implementing the script right, but when I try to set the basis of the character's transform to the resulting basis I'm getting "Condition det == 0 is true" and "invert: Condition det == 0 is true" as errors every physics frame when move_and_slide runs.

                All I did was add the function, created a basis object, set it to the return value of the function, then I set transform.basis to that object.

                I actually did run across another post where you were helping someone with this error, do you have any clue as to what's causing it (Or if I did something wrong)?

                • xyz replied to this.

                  PowerUpT You're probably not passing the right arguments in. Let's see the code.

                    xyz

                    
                    @onready var cameraRig = $CameraRig
                    @onready var camera = $CameraRig/Camera3D
                    
                    const JumpVelocity = 16
                    const Acceleration = 0.2
                    const Deceleration = 1.5
                    const TopSpeed = 15.0
                    const TempGravity = 55.0
                    const SmoothingTime = 4.0
                    
                    var lookSensitity : float = ProjectSettings.get_setting("player/look_sensitivity")
                    var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity")
                    
                    var floorNormal : Vector3 = Vector3()
                    var floorBasis : Basis = Basis()
                    var floorAngle : float = 0.0
                    
                    func _physics_process(delta):
                    	var new_velocity : Vector3 = velocity
                    
                    	if(is_on_floor()):
                    		if (Input.is_action_just_pressed("ui_accept")):
                    			new_velocity.y = JumpVelocity
                    		floorAngle = get_floor_angle()
                    		
                    		if (floorAngle > 0.4):
                    			floorNormal = get_floor_normal()
                    		else:
                    			floorNormal = Vector3.UP
                    	else:
                    		new_velocity.y -= TempGravity * delta
                    		floorNormal = Vector3.UP
                    		
                    	var inputDir : Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
                    	var direction : Vector3 = (transform.basis * Vector3(inputDir.x, 0, inputDir.y)).normalized().rotated(Vector3.UP, cameraRig.rotation.y)
                    	if (direction != Vector3.ZERO):
                    		var xVelNew : float = new_velocity.x + direction.x * Acceleration
                    		var zVelNew : float = new_velocity.z + direction.z * Acceleration
                    		new_velocity.x = clamp(xVelNew, -TopSpeed, TopSpeed)
                    		new_velocity.z = clamp(zVelNew, -TopSpeed, TopSpeed)
                    	else:
                    		new_velocity.x = move_toward(velocity.x, 0, Deceleration)
                    		new_velocity.z = move_toward(velocity.z, 0, Deceleration)
                    			
                    	velocity = new_velocity
                    	
                    	floorBasis = calc_basis(get_floor_normal(), camera.rotation)
                    	
                    	transform.basis = floorBasis
                    	move_and_slide()
                    	
                    func _input(event):
                    	if(event is InputEventMouseMotion):
                    		cameraRig.rotate_y(-event.relative.x * lookSensitity)
                    		camera.rotate_x(-event.relative.y * lookSensitity)
                    		var x : float = clamp(camera.rotation.x, -PI / 2, PI / 2)
                    		camera.rotation = Vector3(x, camera.rotation.y, camera.rotation.z)
                    
                    func calc_basis(floor_normal: Vector3, camera_look: Vector3) -> Basis:
                    	var b = Basis()
                    	b.y = floor_normal
                    	b.z = Plane(b.y).project(camera_look)
                    	b.x = b.y.cross(b.z)
                    	return b.orthonormalized()

                    Here is my code.
                    I did fix the camera so that it's on its own rig instead of the collider, but that shouldn't affect things.

                    I don't think I put the wrong arguments in. I'm passing a vector that keeps the floor normal if the floor angle is past a certain point, and points up otherwise. I did verify this by just passing in get_floor_normal() with the same results. And as for camera_look I'm just passing in the camera's rotation.

                    • xyz replied to this.

                      PowerUpT The arguments are not correct.

                      Firstly get_floor_normal() will return the null vector if the body is not on floor. This will cause that error and the calculated basis will not be valid. It's likely that the body is not on floor in the first frame. And once you assign the invalid basis, your whole transformation system will fall apart from that point onward. If the body is not on floor, we want to keep the existing basis up vector so best to pass that.

                      And secondly, you need to pass a vector as the camera look, not scalar rotation value. The camera always looks down its local negative z axis so you need to pass that vector. Note that my example function expect the reverse look vector.

                      Both those vectors need to be in the same coordinate space. Since get_floor_normal() returns the normal in global space, the look vector needs to be in global space as well.

                      floorBasis = calc_basis(get_floor_normal() if is_on_floor() else global_basis.y, camera.global_basis.z)
                      global_basis = floorBasis

                      You'll also have problems with how you construct the velocity vector, but let's fix the basis part first.

                        xyz Alright, it seems to all be in order now, thanks so much!

                        There was an issue with this code however, making it the camera's global basis actually made the camera spin. I fixed it by making it the camera's local basis (camera.basis.z). Might have to do with the camera being encapsulated in a Node3D to fix the rotation issue.

                        Thanks so much!

                        • xyz replied to this.

                          PowerUpT No, it should be global camera basis. Local basis may work, depending on your rig but best to use global basis. If that's causing problems, it's because other parts of your code are not correct as well. You also don't really need that additional node if the basis is properly calculated.

                          Other things.

                          You need to change the up_direction property of the character body to coincide with the floor normal (or floorBasis.y) so that move_and_slide() works as expected when floor plane is changed.

                          You also cannot calculate gravity like that by altering the velocity's y component directly, because the basis up vector will not be collinear with the global y axis when the basis is rotated. The gravity (and the jump component of the velocity) need to act along the new basis y vector.

                          PowerUpT On the second though, if you allow for a large up/down look angle, the thing will be more stable if you do use a separate node to only handle y rotation (cameraRig) and pass its global_basis.z to be projected on the floor:

                          floorBasis = calc_basis(get_floor_normal() if is_on_floor() else global_basis.y, cameraRig.global_basis.z)

                          This will also allow for the transition between larger relative floor angles without glitches.