godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Allow to get multiple coordinates of vectors at the same time (swizzling) #727

Open Nolkaloid opened 4 years ago

Nolkaloid commented 4 years ago

Describe the project you are working on: 3D top down shooter

Describe the problem or limitation you are having in your project: Operations on different vectors become rather cumbersome when working separately on coordinates:

velocity = Vector3(6, 1, 6)
velocity.x *= 2
velocity.z *= 2

Describe the feature / enhancement and how it helps to overcome the problem or limitation: It would be really useful to be able to access multiple coordinates at the same time like in GLSL:

velocity = Vector3(6, 1, 6)
velocity.xz *= 2
Xrayez commented 4 years ago

You can look at my VectorResource plugin (AssetLib link, but I recommend downloading from GitHub as it contains some recent fixes), see "vector swizzling syntax" in the examples section.

It doesn't support overloaded operators though (nor GDScript: godotengine/godot#23488), so the way you show it in the snippet (namely *=, only = operator is supported being the _set method) is not completely possible, but it's indeed possible to get any combination of a vector (not sure about the performance though). The vector used internally is always Vector3 because it can contain Vector2 representation within itself quite nicely.

Of course it's equally cumbersome if you need to perform this kind of operation frequently enough, because you'd basically have to instantiate an entire Resource just for the vector math:

var velocity = VectorResource.new()
velocity.xyz = Vector3(6, 1, 6)
velocity.xz = velocity.xz * 2

So see for yourself, this has mostly editing/storage purpose.

The plugin originates from #397.

Nolkaloid commented 4 years ago

Thanks for linking the plugin! It's great that there is at least a way to achieve "vector swizzling syntax" right now. But it's again rather cumbersome to use a plugin for something like that. My proposal is just a "small improvement" to gdScript and it's just a question of "quality of life".

Xrayez commented 4 years ago

Yeah I'm just showcasing the hurdles as a plugin writer. Having this implemented in core would certainly make plugin development much easier in this regard.

bojidar-bg commented 4 years ago

Related to https://github.com/godotengine/godot/issues/6861.

Calinou commented 4 years ago

How difficult would this be to implement? Would it just be a matter of adding xz, xy and yz properties to Vector3 with setters and getters that return Vector2?

bojidar-bg commented 4 years ago

@Calinou In essence, yes. This get more contrived since we should probably add zx, yx and zy as well. Then, the swizzles xzy, yzx, yxz, zxy, and zyx are a natural extension of it. Then on Vector2, we would want to have yx for feature parity. And for converting a vector2 into a vector3 we might also want to have xyo, xoy, oxy, yxo, yox, and oyx. (for feature parity, it might be worth adding swizzles with o to vector3 as well, but those are around 18)

Calinou commented 4 years ago

@bojidar-bg Are swizzles featuring all components really useful though (like zyx)?

bojidar-bg commented 4 years ago

Indeed, I can't think of a use for three-component swizzles right now.

Maybe someone else can suggest some usecases for those? :smiley:

Nolkaloid commented 4 years ago

It could be useful if you want to change the orientation of your frame. Let's say I have var =Vector3(1, 4, 1) and then I change it's orientation like this : var = var.xzy

rcorre commented 4 years ago

What about:

enum Component {
    ZERO,
    AXIS_X,
    AXIS_Y,
    AXIS_Z
}

Vector2 Vector3::swizzle2( Component x, Component y );
Vector3 Vector3::swizzle3( Component x, Component y, Component z );

It is slightly clumsier than .xyz(), but still adds a fair bit of convenience without exploding the API with 27 new properties :)

We could even append ZERO to enum Axis but it might not make sense in the other contexts it is used.

Vivraan commented 4 years ago

Could this be done ~using a union access to the Vector's components as an array and~ using an overloaded access([]) operator/new function (granted it's confusing for people who use mutlidimensional array index operators, like in C#)?

var vec3: Vector3
var vec2: Vector2
vec3[X] = 1
vec3[Y, Z] = Vector2(2, 1) # or [2, 1] since both are iterators of some kind
vec2 = vec3[Z, Y]

Then introducing the syntactic sugar vec3.zy for this new operator/function? I have seen rorre's implementation and it felt quite cumbersome in comparison.

rcorre commented 4 years ago

@ShivamMukherjee I believe that would be a more significant change, as:

Vivraan commented 4 years ago

@ShivamMukherjee I believe that would be a more significant change, as:

  • gdscript doesn't have a multi-element indexing operator (that I'm aware of)
  • What are X and Y in your example? In that context they will be interpreted as variables, not symbols. Unless we add X Y Z constants to the global namespace, it would be more like vec3[Vector3.AXIS_Y, Vector3.AXIS_Z]

They're constants basically, could very well be the respective class's AXIS_* constants. The ordering of the constants in this function/operator call will matter. The bottom line is that a notation which is fairly uniform may be easily macro'd from the respective shader swizzle notation.

I suggest an operator since a function call, as far as I understand, can't appear on the left-side of an assignment operator. Again, this could be a setter function kinda thing as well.

aaronfranke commented 3 weeks ago

For reference, here are links to PRs discussing this same topic: https://github.com/godotengine/godot/pull/39674 https://github.com/godotengine/godot/pull/51165 https://github.com/godotengine/godot/pull/55220 https://github.com/godotengine/godot/pull/93012

My take is that because converting is usually not as simple as just getting multiple coordinates in a swizzle (ex: converting between 2D and 3D you need to negate the Y axis and scale by 100, or whatever pixels-per-meter your project is using), and due to the inevitable combinatorial explosion (imagine doing this for Vector4), the engine should not provide these functions/properties.

Calinou commented 3 weeks ago

the engine should not provide these functions/properties.

I agree this would be better implemented at a GDScript level, although I wonder how this would be done in C#. This is likely how GLSL compilers handle it internally (maybe look at Mesa for answers 🙂).

aaronfranke commented 3 weeks ago

@Calinou In C#, this feature is needed in the engine even less because C# has a feature called extension methods. These are defined in users' own code, saving the engine from bloat, but behave the same way as a built-in method, providing a seamless user experience. For example:

// Define this anywhere in your project.
public static Vector2 to_2d(this Vector3 v) {
    return new Vector2(v.X, -v.Y) * 100.0f;
}

// Then use it like this:
Vector2 v2 = v3.to_2d();
Nitbandier commented 2 weeks ago

for starters having just the three xy, yz and xz would be nice and we can discuss the other swizzles later.

or we can get a function with 27 enums,like so:

    var vector_three := vector3.ONE
    vector_three.get_axis(vector3.AXIS_XY)

or a function that get a string like get_axis("XY") or someting like these, but even so i suggest the xy,yz and xz still be added as properties to the vector3 as i think these are more usefull.