ramokz / phantom-camera

👻🎥 Control the movement and dynamically tween 2D & 3D cameras. Built for Godot 4. Inspired by Cinemachine.
https://phantom-camera.dev/
MIT License
2.18k stars 72 forks source link

Expose the Quaternion representation of the 3rd Person Spring Arm #171

Closed ZenithStar closed 8 months ago

ZenithStar commented 9 months ago

Project Type

3D

Feature Description

Quaternions represent the "orientation" of a 3D body, as opposed to the euler angle, which represent "rotation". This is the difference between representing "position" as opposed to "displacement" and is the ideal way of working with the orientation.

Here's an example of 3rd person camera rotation code using quaternions instead of euler angles

func rotate_camera(input: Vector2):
    var frame_pitch = Quaternion(Vector3.RIGHT, -input.y )
    var frame_yaw =  Quaternion(Vector3.UP, -input.x)
    var euler = (pcam._follow_spring_arm_node.quaternion * frame_pitch).get_euler(EULER_ORDER_YXZ)
    if abs(euler.x) >= pitch_clamp:
        pcam._follow_spring_arm_node.quaternion = frame_yaw * pcam._follow_spring_arm_node.quaternion
    else:
        pcam._follow_spring_arm_node.quaternion = frame_yaw * pcam._follow_spring_arm_node.quaternion * frame_pitch

As a bonus to this issue, maybe also expose the euler order at rotation_order. I don't need this for what I'm doing, but perhaps in the future, someone will.

Use Cases

When working with euler angles, you run the risk of encountering gimble lock bugs. https://en.wikipedia.org/wiki/Gimbal_lock Quaternion multiply is a linear, branch-free operation, as opposed to having to use a clamp or wrap, which are branching operations. Theoretically, this makes quaternion multiply more efficient. If you're working with fully unlocked 6DoF motion (e.g. a spacecraft or fighter jet), as opposed to grounded, roll-locked motion, you'll most certainly need to use quaternions.

Importance

Low - there are workarounds where the proposed feature would just simplify things

Usage

Often - a significant amount of projects can find this useful

(Optional) Proposed Solution

I haven't looked through what all you'd need to add to phantom_camera_properties.gd, but for phantom_camera_3D.gd

## Assigns new quaternion orientation value to SpringArm for Third Person Follow mode.
func set_third_person_quaternion(value: Quaternion) -> void:
    _follow_spring_arm_node.quaternion = value
## Gets the quaternion orientation from the SpringArm for Third Person Follow mode.
func get_third_person_quaternion() -> Quaternion:
    return _follow_spring_arm_node.quaternion

Alternatively, it might be better to just directly expose a reference to the SpringArm3D node. My current workaround is to just directly access $PhantomCamera3D._follow_spring_arm_node

ramokz commented 9 months ago

That's a good shout. Thanks for the suggestion.

Your proposed solution is pretty much what it needs to be to support that.

Alternatively, it might be better to just directly expose a reference to the SpringArm3D node. My current workaround is to just directly access $PhantomCamera3D._follow_spring_arm_node

Think this is also a solid idea. Kept it private initially to avoid complicating things, but it won't hurt to expose it either way.