Browse Source

Merge pull request #90464 from timothyqiu/quat-to

[3.x] Allow constructing Quat from two Vector3s
lawnjelly 2 months ago
parent
commit
a24e93af4b
3 changed files with 57 additions and 19 deletions
  1. 46 19
      core/math/quat.h
  2. 2 0
      core/variant_call.cpp
  3. 9 0
      doc/classes/Quat.xml

+ 46 - 19
core/math/quat.h

@@ -71,6 +71,50 @@ public:
 		r_axis.z = z * r;
 	}
 
+	bool set_shortest_arc(const Vector3 &p_from, const Vector3 &p_to) {
+#ifdef MATH_CHECKS
+		ERR_FAIL_COND_V_MSG(p_from.is_zero_approx() || p_to.is_zero_approx(), false, "The vectors must not be zero.");
+#endif
+#ifdef REAL_T_IS_DOUBLE
+		constexpr real_t ALMOST_ONE = 0.999999999999999;
+#else
+		constexpr real_t ALMOST_ONE = 0.99999975f;
+#endif
+		Vector3 n0 = p_from.normalized();
+		Vector3 n1 = p_to.normalized();
+
+		real_t d = n0.dot(n1);
+		if (Math::abs(d) > ALMOST_ONE) {
+			if (d >= 0) {
+				// Vectors point to the same direction.
+				x = 0;
+				y = 0;
+				z = 0;
+				w = 1;
+				return true;
+			}
+
+			// Get any perpendicular vector.
+			Vector3 axis = n0.cross((Math::abs(n0.x) <= Math::abs(n0.y) && Math::abs(n0.x) <= Math::abs(n0.z)) ? Vector3(1, 0, 0) : Vector3(0, 1, 0)).normalized();
+			x = axis.x;
+			y = axis.y;
+			z = axis.z;
+			w = 0;
+			return false;
+		}
+
+		Vector3 c = n0.cross(n1);
+		real_t s = Math::sqrt((1 + d) * 2);
+		real_t rs = 1 / s;
+
+		x = c.x * rs;
+		y = c.y * rs;
+		z = c.z * rs;
+		w = s * 0.5f;
+		normalize();
+		return true;
+	}
+
 	void operator*=(const Quat &p_q);
 	Quat operator*(const Quat &p_q) const;
 
@@ -135,25 +179,8 @@ public:
 		return *this;
 	}
 
-	Quat(const Vector3 &p_v0, const Vector3 &p_v1) // shortest arc
-	{
-		Vector3 c = p_v0.cross(p_v1);
-		real_t d = p_v0.dot(p_v1);
-
-		if (d < -1 + (real_t)CMP_EPSILON) {
-			x = 0;
-			y = 1;
-			z = 0;
-			w = 0;
-		} else {
-			real_t s = Math::sqrt((1 + d) * 2);
-			real_t rs = 1 / s;
-
-			x = c.x * rs;
-			y = c.y * rs;
-			z = c.z * rs;
-			w = s * 0.5f;
-		}
+	Quat(const Vector3 &p_v0, const Vector3 &p_v1) {
+		set_shortest_arc(p_v0, p_v1);
 	}
 
 	inline Quat() :

+ 2 - 0
core/variant_call.cpp

@@ -518,6 +518,7 @@ struct _VariantCall {
 	VCALL_LOCALMEM0R(Quat, get_euler);
 	VCALL_LOCALMEM1(Quat, set_euler);
 	VCALL_LOCALMEM2(Quat, set_axis_angle);
+	VCALL_LOCALMEM2R(Quat, set_shortest_arc);
 
 	VCALL_LOCALMEM0R(Color, to_argb32);
 	VCALL_LOCALMEM0R(Color, to_abgr32);
@@ -1907,6 +1908,7 @@ void register_variant_methods() {
 	ADDFUNC0R(QUAT, VECTOR3, Quat, get_euler, varray());
 	ADDFUNC1(QUAT, NIL, Quat, set_euler, VECTOR3, "euler", varray());
 	ADDFUNC2(QUAT, NIL, Quat, set_axis_angle, VECTOR3, "axis", REAL, "angle", varray());
+	ADDFUNC2R(QUAT, BOOL, Quat, set_shortest_arc, VECTOR3, "from", VECTOR3, "to", varray());
 
 	ADDFUNC0R(COLOR, INT, Color, to_argb32, varray());
 	ADDFUNC0R(COLOR, INT, Color, to_abgr32, varray());

+ 9 - 0
doc/classes/Quat.xml

@@ -126,6 +126,15 @@
 				Sets the quaternion to a rotation specified by Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle).
 			</description>
 		</method>
+		<method name="set_shortest_arc">
+			<return type="bool" />
+			<argument index="0" name="from" type="Vector3" />
+			<argument index="1" name="to" type="Vector3" />
+			<description>
+				Makes this quaternion represent the shortest arc between [code]from[/code] and [code]to[/code]. These can be imagined as two points intersecting a sphere's surface, with a radius of [code]1.0[/code].
+				If the input vectors point in opposite directions, the rotation axis that produces the shortest arc is not unique. In this case, this method will automatically select one and return [code]false[/code]. If at least one of the input vectors is approximately zero, an error is printed and it returns [code]false[/code]. Otherwise, it always returns [code]true[/code].
+			</description>
+		</method>
 		<method name="slerp">
 			<return type="Quat" />
 			<argument index="0" name="to" type="Quat" />