godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.36k stars 21.06k forks source link

Camera2D get_camera_screen_center() reports bad position with rotating camera #44358

Closed DragonAxe closed 1 year ago

DragonAxe commented 3 years ago

Godot version: Godot Engine v3.2.3.stable.official

OS/device including version: Arch Linux 5.9.11-arch2-1 #1 SMP PREEMPT Sat, 28 Nov 2020 02:07:22 +0000 x86_64 GNU/Linux OpenGL ES 3.0 Renderer: Mesa Intel(R) UHD Graphics 620 (KBL GT2)

Issue description: What happened: Camera2D method get_camera_screen_center() does not return (0, 0) when the camera is being rotated with self.rotate(0.1*delta), Offset=(0, 0), Current=On, Rotating=On, and Anchor Mode = Drag Center. Note: With Anchor Mode = Fixed TopLeft I also see unexpected behavior where the position reported by get_camera_screen_center() remains unchanged even though the camera view is rotating about the top left corner.

What I expected: get_camera_screen_center() should continue to return (0, 0) at any rotation angle as long as Offset=(0, 0) and the Anchor Mode = Drag Center. Note: With Anchor Mode = Fixed TopLeft the position reported by get_camera_screen_center() should follow a circular path with center at the top left corner of the camera and radius equal to half the diagonal length of the camera window size.

https://docs.godotengine.org/en/stable/classes/class_camera2d.html#class-camera2d-method-get-camera-screen-center

Steps to reproduce:

func _ready(): set_process(true)

func _process(delta): self.rotate(0.1*delta) $"../Label".text = str(self.get_camera_screen_center())


* Run.
  You should see the label in the center of the screen rotating while displaying the output of `get_camera_screen_center()`.

**Minimal reproduction project:**
[bug_cam_screen_center.zip](https://github.com/godotengine/godot/files/5686900/bug_cam_screen_center.zip)
<!-- A small Godot project which reproduces the issue. Drag and drop a zip archive to upload it. -->
DragonAxe commented 3 years ago

With the following project settings: Window Width/Height = (800, 600), Stretch Mode = viewport, Stretch Aspect = keep. I observed the following approximate results from get_camera_screen_center() when the camera is rotated at the following angles at camera position or offset (0, 0):

Note: Screen center values are approximate and are rounded to remove floating point math artifacts, e.g. (50.000061, -0.000061).

DragonAxe commented 3 years ago

For anyone else who has run into this issue, I found a workaround:

var inv_canv_tfm: Transform2D = self.get_canvas_transform().affine_inverse()
var half_screen: Transform2D = Transform2D().translated(get_viewport_rect().size / 2)
var actual_screen_center_pos: Vector2 = inv_canv_tfm * half_screen * Vector2(0, 0)

actual_screen_center_pos will be accurate even with camera smoothing enabled, zoom, and rotation. (Note: Did not test with camera offset)

juse4pro commented 3 years ago

@DragonAxe I tried to adapt your code to my game. But I can currently not multiply a vector with a transform (at least in my C# project). I don't actually understand the third line. Could you explain it so I can adapt?

DragonAxe commented 3 years ago

Let me see if I can explain, it's been a while since I've looked at this.

Basically what I'm doing here is matrix-vector multiplication. One thing to note is that GDscript has some special handling when using * with vectors and matrices. I haven't done much with C#, but perhaps the Transform2D class has a .Multiply(<vector>) method you could use, or maybe .xform() and .xform_inv() methods are what you need. (https://docs.godotengine.org/en/3.2/tutorials/math/matrices_and_transforms.html#converting-positions-between-transforms)

Here is a breakdown of my code above:

self.get_canvas_transform() I start by getting a transformation matrix that can convert a vector from world space into screen space.

self.get_canvas_transform().affine_inverse() Then I make it go in the opposite direction by inverting the matrix. (screen space to world space)

Transform2D().translated(get_viewport_rect().size / 2) To make the next operation a simple matrix multiplication, I create another transformation matrix which, when multiplied with a vector, will move that vector to the center of the screen.

half_screen * Vector2(0, 0) Then, I take a position at (0,0) then I move it (translate it) into the center of the screen. This gives us a vector that exists in screen space.

inv_canv_tfm * Then I can use my handy screen space to world space transformation matrix to move the center screen position to the position of the camera.

Hope this helps!

juse4pro commented 3 years ago

Thank you for the explaination. I think I now understand it. I adapted the code to C#:

Transform2D inverseCanvasTransform = this.GetCanvasTransform().AffineInverse();
Transform2D halfScreenTransform = (new Transform2D()).Translated(this.GetViewportRect().Size * 0.5f);
Vector2 actualScreenCenterPosition = inverseCanvasTransform.Xform(halfScreenTransform.Xform(Vector2.Zero));

I tested if this works with my implemented camera shake. But I guess there is a problem left with the offset (as you mentioned already), because the position seems to be off when my camera shakes.

But maybe I can find a way to fix this. :)

awardell commented 1 year ago

I had accidentally made a duplicate of this. Reposting as a comment here in case my repro might be useful.

Godot version

v4.2.beta.custom_build [b1371806a], v4.1.2.stable

System information

Godot v4.2.beta (b1371806a) - Windows 10.0.19045 - GLES3 (Compatibility) - NVIDIA GeForce GTX 1080 Ti (NVIDIA; 31.0.15.3623) - AMD Ryzen Threadripper 1950X 16-Core Processor (32 Threads)

Issue description

The Vector2 returned by Camera2D::get_screen_center_position() is (incorrectly) affected by the camera's rotation. Rotating the camera will cause get_screen_center_position to return different values, despite the actual center being in the same place.

Steps to reproduce

  1. Rotate a camera to non-identity.
  2. get_screen_center_position returns incorrect values.

Minimal reproduction project

In this example project there are two scenes with similar setups. One Sprite, colored red, follows the Camera2D's get_screen_center_position() The other Sprite follows the Camera2D's get_target_position() for comparison The Camera2D being followed is rotated by delta in _process

one_cam.tscn is from the perspective of the rotating camera. two_cams..tscn is from the perspective of a second, zoomed out camera.

ReproProject.zip

awardell commented 1 year ago

I've looked at https://github.com/godotengine/godot/pull/63773 and it will unfortunately NOT close this issue, though I don't believe it was intended to. I agree that if that gets merged, it will at least make it easier to fix this issue.

kleonc commented 1 year ago

I adapted the code to C#:

Transform2D inverseCanvasTransform = this.GetCanvasTransform().AffineInverse();
Transform2D halfScreenTransform = (new Transform2D()).Translated(this.GetViewportRect().Size * 0.5f);
Vector2 actualScreenCenterPosition = inverseCanvasTransform.Xform(halfScreenTransform.Xform(Vector2.Zero));

In C# default constructor zeroes the structs. In GDScript Transform2D() == Transform2D.IDENTITY but in C# new Transform2D() != Transform2D.Identity. So replacing new Transform2D() with Transform2D.Identity should fix that snippet.

Anyway, pure translation transform can be omitted by straight up adding the translation to the vector, same effect:

Transform2D inverseCanvasTransform = this.GetCanvasTransform().AffineInverse();
Vector2 actualScreenCenterPosition = inverseCanvasTransform.Xform(this.GetViewportRect().Size * 0.5f);