Repository: godotengine/godot (C++)

Issue:
MotionCollision get_angle() returns NAN
### Godot version

master (e684d126ed6)

### System information

Windows 10

### Issue description

MotionCollision's get_angle() function can return NAN. According to the docs it should always return a positive value. 

I believe I'm running into the same scenario as https://github.com/godotengine/godot/issues/8111 as applying a CLAMP within the acos() seems to fix the issue.


### Steps to reproduce

In my case I had a platform that could rotate along the z axis and an independent character controller who's gravity would automatically adjust to the down vector of the rotating platform and the character controller would rotate towards the gravity so it's down vector also aligned with the platform's.

Using get_angle against the ground from the player's up direction would occasionally result in NAN at various platform rotations despite the vectors being perpendicular and normalized.

It would also seemingly result in weird behavior in move_and_slide() where the velocity would continuously have gravity added to it despite the character controller not moving. 

In the minimal reproduction project there's a single scene with a platform that's named something along the lines of rotate me. If you run that project and slowly rotate the z axis from the remote tree eventually you should see an instance where "nan!" gets printed indicating it ran into the issue. It's very finicky and the character controller can fall through the platform so it might take a few tries.

### Minimal reproduction project

[GetAngle.zip](https://github.com/godotengine/godot/files/11424308/GetAngle.zip)



Discussion: Ah, this has come up before, imo we should be using a `safe_acos()` function in the majority of cases in the engine (with input clamped). It's very easy for float error to get outside the -1 to 1 range and cause Nan, and clamping is pretty cheap outside bottleneck code.

EDIT:
Yeah this bug is everywhere. In Godot case I think it might be better to make the default function safe, and add an extra `unsafe_acos` function for the rare occasions where it isn't needed. Same applies for `asin`, not sure if there is a similar conditions for other math funcs (maybe `safe_sqrt`), but I've most commonly seen it occur for `acos`.



Fix (patch):
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 95a4187062f3..bfd902c7e287 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -807,8 +807,8 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
 	z = (rows[1][0] - rows[0][1]) / s;
 
 	r_axis = Vector3(x, y, z);
-	// CLAMP to avoid NaN if the value passed to acos is not in [0,1].
-	r_angle = Math::acos(CLAMP((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2, (real_t)0.0, (real_t)1.0));
+	// acos does clamping.
+	r_angle = Math::acos((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2);
 }
 
 void Basis::set_quaternion(const Quaternion &p_quaternion) {
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 078320d62030..f96d3a909fba 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -74,11 +74,13 @@ class Math {
 	static _ALWAYS_INLINE_ double tanh(double p_x) { return ::tanh(p_x); }
 	static _ALWAYS_INLINE_ float tanh(float p_x) { return ::tanhf(p_x); }
 
-	static _ALWAYS_INLINE_ double asin(double p_x) { return ::asin(p_x); }
-	static _ALWAYS_INLINE_ float asin(float p_x) { return ::asinf(p_x); }
+	// Always does clamping so always safe to use.
+	static _ALWAYS_INLINE_ double asin(double p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asin(p_x)); }
+	static _ALWAYS_INLINE_ float asin(float p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asinf(p_x)); }
 
-	static _ALWAYS_INLINE_ double acos(double p_x) { return ::acos(p_x); }
-	static _ALWAYS_INLINE_ float acos(float p_x) { return ::acosf(p_x); }
+	// Always does clamping so always safe to use.
+	static _ALWAYS_INLINE_ double acos(double p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acos(p_x)); }
+	static _ALWAYS_INLINE_ float acos(float p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acosf(p_x)); }
 
 	static _ALWAYS_INLINE_ double atan(double p_x) { return ::atan(p_x); }
 	static _ALWAYS_INLINE_ float atan(float p_x) { return ::atanf(p_x); }
diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp
index 34e212a5b65b..e4ad17c8ef89 100644
--- a/core/math/quaternion.cpp
+++ b/core/math/quaternion.cpp
@@ -35,7 +35,8 @@
 
 real_t Quaternion::angle_to(const Quaternion &p_to) const {
 	real_t d = dot(p_to);
-	return Math::acos(CLAMP(d * d * 2 - 1, -1, 1));
+	// acos does clamping.
+	return Math::acos(d * d * 2 - 1);
 }
 
 Vector3 Quaternion::get_euler(EulerOrder p_order) const {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index a0b623936a6b..86f9e5ebb3a3 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -65,7 +65,7 @@
 			<return type="float" />
 			<param index="0" name="x" type="float" />
 			<description>
-				Returns the arc cosine of [param x] in radians. Use to get the angle of cosine [param x]. [param x] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method acos] will return [constant @GDScript.NAN].
+				Returns the arc cosine of [param x] in radians. Use to get the angle of cosine [param x]. [param x] will be clamped between [code]-1.0[/code] and [code]1.0[/code] (inclusive), in order to prevent [method acos] from returning [constant @GDScript.NAN].
 				[codeblock]
 				# c is 0.523599 or 30 degrees if converted with rad_to_deg(c)
 				var c = acos(0.866025)
@@ -76,7 +76,7 @@
 			<return type="float" />
 			<param index="0" name="x" type="float" />
 			<description>
-				Returns the arc sine of [param x] in radians. Use to get the angle of sine [param x]. [param x] must be between [code]-1.0[/code] and [code]1.0[/code] (inclusive), otherwise, [method asin] will return [constant @GDScript.NAN].
+				Returns the arc sine of [param x] in radians. Use to get the angle of sine [param x]. [param x] will be clamped between [code]-1.0[/code] and [code]1.0[/code] (inclusive), in order to prevent [method asin] from returning [constant @GDScript.NAN].
 				[codeblock]
 				# s is 0.523599 or 30 degrees if converted with rad_to_deg(s)
 				var s = asin(0.5)