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

Vector circle-square mapping methods #95

Open
cgbeutler opened this issue Apr 8, 2021 · 7 comments
Open

Vector circle-square mapping methods #95

cgbeutler opened this issue Apr 8, 2021 · 7 comments

Comments

@cgbeutler
Copy link

cgbeutler commented Apr 8, 2021

I just found this repo. My personal vector helper class has the following functions that could be useful to others.

The controllers in Godot give you a value from -1 to 1 for both the x and y. Some math operations require a more normalized version of the input, but just doing vector.normalized() chops the vector short instead of properly scaling it. In other words, you get this:
image

The more proper way to do it and eliminate these "dead zones" in the corners, is to map the square space to a circle space.
image

Inversely, mapping the circle-based normals to a square would look like this:
image

For a bit more info on the math, this blog post does an ok job explaining: http://squircular.blogspot.com/2015/09/mapping-circle-to-square.html

Below is my implementation. Feel free to tweak it to make it faster.

const __2root2 := 2.0 * sqrt(2.0)

# Map a circle grid to a square grid
# input: vector from a circular domain with radius of 1
# output: vector in a square domain from (-1,-1) to (1,1)
static func map_circle_to_square( xy :Vector2 ) -> Vector2:
	var x2 := xy[0]*xy[0]
	var y2 := xy[1]*xy[1]
	return Vector2(
		0.5 * (sqrt(2.0 + x2 - y2 + xy[0] * __2root2) - sqrt(2.0 + x2 - y2 - xy[0] * __2root2)),
		0.5 * (sqrt(2.0 - x2 + y2 + xy[1] * __2root2) - sqrt(2.0 - x2 + y2 - xy[1] * __2root2))
	)

# Map a square grid to a circular grid
# input: vector from a square domain from (-1,-1) to (1,1)
# output: vector in a circle domain with radius of 1
static func map_square_to_circle( xy :Vector2 ) -> Vector2:
	return Vector2(
		xy.x * sqrt(1.0 - xy.y*xy.y/2.0),
		xy.y * sqrt(1.0 - xy.x*xy.x/2.0)
	)

Also, if this doesn't seem useful to anyone else, feel free to scrap this issue. I just know these took me a long time to find and figure out.

@nonunknown
Copy link
Contributor

could you please provide some use-cases?

@cgbeutler
Copy link
Author

cgbeutler commented Apr 8, 2021

could you please provide some use-cases?

I use it most for linear motion (like in a top-down game.)
You can do the square-to-circle, then multiply that by the move speed and you get smooth top-down motion based on how far the stick is moved.

If you were to just multiply the square-space vector, then the character would move faster diagonally. Normalize the vector and you get deadzones where your char is at max speed, despite not pressing the stick all the way to a corner.

@nonunknown
Copy link
Contributor

ohhh so this solution basically lerps from 0 to 1, but according from how far the analogic is from the center...

@cgbeutler
Copy link
Author

ohhh so this solution basically lerps from 0 to 1, but according from how far the analogic is from the center...

Yeah, basically. It let's you use an analog as a kind of scalar vector. Some game engines report analog in the circular space to begin with, so the length can only ever be 1. Godot reports x and y separately, so you can get a length of sqrt(2) or 1.414 when moving diagonally.

@Xrayez
Copy link
Contributor

Xrayez commented Apr 11, 2021

Seems useful once you figure this out and why someone would need this, but I'm afraid that kind of attention to detail won't matter to most devs. Yet it doesn't mean that we can't promote those math tricks to game devs. 🙂

Looking at the blog post you linked, it seems to me that it might also be possible to do some image processing with this in GDScript...

That said, this is actually better be available directly in the engine core to work transparently for the input system.

@Xrayez
Copy link
Contributor

Xrayez commented May 6, 2021

See also Godot contributors chat discussion: https://chat.godotengine.org/channel/devel?msg=rpEQraki4hKZfuQhn.

It seems like the problem is that some gamepads report 1.0 strength on each axis when pushed diagonally, while some might not... This might also signify Godot's input system has a bug, but I cannot say this for sure since I'm not an expert in this...

Godot relies on SDL2 gamepad mappings as well.

@cgbeutler
Copy link
Author

cgbeutler commented May 6, 2021

It seems like the problem is that some gamepads report 1.0 strength on each axis when pushed diagonally...

Huh, I just assumed it was square-space by design, but it may just be my cheap WalMart controller!? If some report square and some circle, then that is indeed very buggy.

Either way, the conversions are useful. Circle space for top-down movement, square for side scroller movement. (At least those are the most common choices I've seen.)

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

No branches or pull requests

4 participants