godot-extended-libraries / godot-next

Godot Node Extensions - Basic Node Extensions for Godot Engine
MIT License
957 stars 61 forks source link

Vector circle-square mapping methods #95

Open cgbeutler opened 3 years ago

cgbeutler commented 3 years ago

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 commented 3 years ago

could you please provide some use-cases?

cgbeutler commented 3 years ago

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 commented 3 years ago

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

cgbeutler commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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