Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
DragonAxe opened this issue Dec 14, 2020 · 8 comments · Fixed by #83427 · May be fixed by #63773
Closed

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

DragonAxe opened this issue Dec 14, 2020 · 8 comments · Fixed by #83427 · May be fixed by #63773

Comments

@DragonAxe
Copy link

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:

  • Create a new 2d scene.
  • Create a Camera2D as a child node of the scene root.
  • Create a Label as a child node of the scene root. (The label is simply for displaying the result of get_camera_screen_center().)
  • Set the following Camera2D settings:
    Anchor Mode: Drag Center
    Rotating: On
    Current: On
  • Attach the following script to the Camera2D node.
extends Camera2D

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

@DragonAxe
Copy link
Author

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):

  • 0: (0, 0)
  • 90: (700, -100)
  • 180: (800, 600)
  • 270: (100,700)
  • 360: (0, 0)
    At camera position or offset (50, 0):
  • 0: (50, 0)
  • 90: (750, -100)
  • 180: (850, 600)
  • 270: (150,700)
  • 360: (50, 0)
    At camera position or offset (0, 50):
  • 0: (0, 50)
  • 90: (700, -50)
  • 180: (800, 650)
  • 270: (100,750)
  • 360: (0, 50)

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

@DragonAxe
Copy link
Author

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
Copy link

@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
Copy link
Author

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
Copy link

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
Copy link
Contributor

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 [b137180], v4.1.2.stable

System information

Godot v4.2.beta (b137180) - 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
Copy link
Contributor

I've looked at #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
Copy link
Member

kleonc commented Oct 16, 2023

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants