godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Add Quat constructor from two Vector3s representing a rotation between them #424

Open ghost opened 4 years ago

ghost commented 4 years ago

Describe the project you are working on: I'm working on a 3D platformer. Running, jumping, falling down bottomless pits for 10 seconds before eventually hitting the death plane, etc. The works.

Describe the problem or limitation you are having in your project: I want to apply the player's velocity perpendicularly to the ground's normal vector. In Unity this is easily done by generating a Quaternion with Quaternion.FromToRotation(fromVector, toVector) -- where fromVector would be Vector3.up and toVector would be the detected ground normal -- then applying the quaternion to the velocity vector. Godot doesn't have an equivalent to Quaternion.FromToRotation(), which is a glaring omission in my eyes.

Describe how this feature / enhancement will help you overcome this problem or limitation: I somehow spent two days figuring out how to replicate this behavior in GDScript, and while I was successful, I was left wondering why it wasn't just a basic Quat constructor to begin with. It's something I took for granted, and I'd expect that most Unity users feel the same way about it. I couldn't find any good resource for dealing with this in Godot, so having to piece it together myself was annoying.

Also, given that a lot of the decent 3D platformer tutorials and projects online are based around Unity, I imagine it's a problem quite a few Godot users would run into as they study those materials.

Show a mock up screenshots/video or a flow diagram explaining how your proposal will work: Well, I don't know how I'd visually represent this, but this is probably what the documentation for it would look like: image

In practice it could be used like this: image

Describe implementation detail for your proposal (in code), if possible: This is the code I ended up with in GDScript:

func quat_from_to_rotation(from, to):
    var axis = from.cross(to).normalized()
    var angle = from.angle_to(to)
    var quat = Quat(axis, angle)
    return quat

All it comes down to is calculating a rotation axis between the two vectors using the cross product (which must be normalized), getting the angle between them, then using the Quat(axis, angle) constructor to get the required quaternion.

If this enhancement will not be used often, can it be worked around with a few lines of script?: Yes, as seen above. I still want to push for it being in core though, because despite how simple the code I ended up with is, I think the fact that people have been making 3D platformers in Unity for years without needing to know how it works speaks to how powerful and harmless a crutch it is.

Is there a reason why this should be core and not an add-on in the asset library?: I guess it would be weird to download an add-on for 5 lines of code... That's all I've got.

Calinou commented 4 years ago

Quat() already has a constructor with two arguments (axis: float and angle: float). GDScript currently doesn't allow having multiple constructors with the same argument count but different types. This is being implemented by https://github.com/godotengine/godot/pull/35698.

jabcross commented 4 years ago

Doesn't have to be a constructor, could be a named static function, right?

Carbonateb commented 3 years ago

I second this, would be great to have it built in. It could also be implemented on the Vector3 class like this: vector_a.from_to_rotation(vector_b)

Unreal Engine has the same thing too, it's called Find Between Orientation Vectors. Not sure what name describes it better, but I think I'd favor calling it the same thing as Unity since there are probably more tutorials that use it. If only there was a way to tag functions with different names for better discoverability...

I think I'll try implementing this, might be a good first contribution to the engine.

fire commented 2 years ago

This has been implemented since godot engine opensource release.

https://github.com/godotengine/godot/blob/master/core/math/quaternion.h#L145

image

marmitoTH commented 1 year ago

Adding more information about use cases, I'll give an example of how it works in Unity when we need to rotate a given transform to the ground normal. The code to get what I want is as simple as this:

transform.rotation = Quaternion.FromToRotation(transform.up, groundNormal) * transform.rotation;

The transform.up is the equivalent of Godot's basis Y. As far as I can tell, there's no way to do this in Godot with the same ease. It seems like I have to reconstruct the transform basis somehow to get the result I want.

I also use this often for transform inputs and camera movement. It works fine for inputs since I can rotate vectors with the Quaternion in Godot. But the camera also relies on multiplying the transform's rotation. I wish it were as easy as it is in Unity.

fire commented 1 year ago

Edited:

image

See also look at https://docs.godotengine.org/en/latest/classes/class_node3d.html#class-node3d-method-look-at

For this look_at function in Godot Engine, there is an implicit Node3D.transform being used.

Edited:

Quaternion FromToRotation(fromVector, toVector)

  1. fromVector is a position.
  2. toVector is a normal rotation which is not a point. Godot Engine typically uses Basis (3x3 matrix) for rotation and not a Vector3 which is overloaded to mean Euler angles, matrix axis headings or a point of a unit sphere.

Edited:

Let me know if this implementation is what is wanted. It's also engineered for an inverse kinematics solver so it's a bit stronger than the interface from Unity suggests.

https://github.com/V-Sekai/many_bone_ik/blob/main/src/math/qcp.cpp The math frightens me.

TEST_CASE("[Modules][QCP] Weighted Superpose") {
    double epsilon = CMP_EPSILON;
    QCP qcp(epsilon);

    Quaternion expected = Quaternion(0, 0, sqrt(2) / 2, sqrt(2) / 2);
    PackedVector3Array moved = { Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(1, 2, 3) };
    PackedVector3Array target = moved;
    for (Vector3 &element : target) {
        element = expected.xform(element);
    }
    Vector<double> weight = { 1.0, 1.0, 1.0 }; // Equal weights

    Quaternion result = qcp.weighted_superpose(moved, target, weight, false);
    CHECK(abs(result.x - expected.x) < epsilon);
    CHECK(abs(result.y - expected.y) < epsilon);
    CHECK(abs(result.z - expected.z) < epsilon);
    CHECK(abs(result.w - expected.w) < epsilon);
}

TEST_CASE("[Modules][QCP] Weighted Translation") {
    double epsilon = CMP_EPSILON;
    QCP qcp(epsilon);

    Quaternion expected;
    PackedVector3Array moved = { Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(1, 2, 3) };
    PackedVector3Array target = moved;
    Vector3 translation_vector = Vector3(1, 2, 3);
    for (Vector3 &element : target) {
        element = expected.xform(element + translation_vector);
    }
    Vector<double> weight = { 1.0, 1.0, 1.0 }; // Equal weights
    bool translate = true;

    Quaternion result = qcp.weighted_superpose(moved, target, weight, translate);
    CHECK(abs(result.x - expected.x) < epsilon);
    CHECK(abs(result.y - expected.y) < epsilon);
    CHECK(abs(result.z - expected.z) < epsilon);
    CHECK(abs(result.w - expected.w) < epsilon);

    // Check if translation occurred
    CHECK(translate);
    Vector3 translation_result = expected.xform_inv(qcp.get_translation());
    CHECK(abs(translation_result.x - translation_vector.x) < epsilon);
    CHECK(abs(translation_result.y - translation_vector.y) < epsilon);
    CHECK(abs(translation_result.z - translation_vector.z) < epsilon);
}