godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.26k stars 101 forks source link

Add method to drop axis from Vector3 #12620

Closed Joy-less closed 3 weeks ago

Joy-less commented 3 weeks ago

Describe the project you are working on

A game with player characters and non-player characters.

Describe the problem or limitation you are having in your project

There is not a clean way to compare two objects horizontally.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add a Flatten/flatten method to Vector3 that allows you to convert it to a Vector2 by removing a certain axis.

Before:

if ((player.Position with { Y = 0 }).DistanceTo(enemy.Position with { Y = 0 }) <= 2.0f) {
    GD.Print("near");
}
if Vector2(player.position.x, player.position.z).distance_to(Vector2(enemy.position.x, enemy.position.z)) <= 2.0:
    print("near")

After:

if (player.Position.Flatten().DistanceTo(enemy.Position.Flatten()) <= 2.0f) {
    GD.Print("near");
}
if player.position.flatten().distance_to(enemy.position.flatten()) <= 2.0:
    print("near")

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

/// <summary>
/// Returns a vector excluding the component at the given axis.
/// </summary>
public static Vector2 Flatten(this Vector3 vector3, Vector3.Axis axis = Vector3.Axis.Y) {
    return axis switch {
        Vector3.Axis.X => new Vector2(vector3.Y, vector3.Z),
        Vector3.Axis.Y => new Vector2(vector3.X, vector3.Z),
        Vector3.Axis.Z => new Vector2(vector3.X, vector3.Y),
        _ => throw new ArgumentException("Bad axis", nameof(axis))
    };
}
## Returns a vector excluding the component at the given axis.
static func flatten(vector3: Vector3, axis: Vector3.Axis = Vector3.Axis.AXIS_Y) -> Vector2:
    match axis:
        Vector3.Axis.AXIS_X:
            return Vector2(vector3.y, vector3.z)
        Vector3.Axis.AXIS_Y:
            return Vector2(vector3.x, vector3.z)
        Vector3.Axis.AXIS_Z:
            return Vector2(vector3.x, vector3.y)
        _:
            push_error("Bad axis (assuming Y)")
            return Vector2(vector3.x, vector3.z)

This could also be implemented by returning a Vector3 where the given axis is zero, but I think it's more useful and performant to return a Vector2.

You could also implement Flatten/flatten for Vector4 -> Vector3, but I don't see a use case for it.

Possible alternative names for the method:

If this enhancement will not be used often, can it be worked around with a few lines of script?

It can be worked around several ways, but deciding which way wastes time and most aren't pretty.

Is there a reason why this should be core and not an add-on in the asset library?

This could benefit a lot of games (since most games don't have free vertical movement) and is very simple.

beicause commented 3 weeks ago

FYI, I usually define the following extension methods to get vector2 from vector3:

    public static Vector2 XY(this Vector3 self) => new(self.X, self.Y);
    public static Vector2 XZ(this Vector3 self) => new(self.X, self.Z);
    public static Vector2 YX(this Vector3 self) => new(self.Y, self.X);
    public static Vector2 YZ(this Vector3 self) => new(self.Y, self.Z);
    public static Vector2 ZX(this Vector3 self) => new(self.Z, self.X);
    public static Vector2 ZY(this Vector3 self) => new(self.Z, self.Y);

    public static Vector2i XY(this Vector3i self) => new(self.X, self.Y);
    public static Vector2i XZ(this Vector3i self) => new(self.X, self.Z);
    public static Vector2i YX(this Vector3i self) => new(self.Y, self.X);
    public static Vector2i YZ(this Vector3i self) => new(self.Y, self.Z);
    public static Vector2i ZX(this Vector3i self) => new(self.Z, self.X);
    public static Vector2i ZY(this Vector3i self) => new(self.Z, self.Y);
Joy-less commented 3 weeks ago

@beicause I do actually like that, even though it's a lot of methods. Would be extra nice as properties:

if (player.Position.XZ.DistanceTo(enemy.Position.XZ) <= 2.0f) {
    GD.Print("near");
}
if player.position.xz.distance_to(enemy.position.xz) <= 2.0:
    print("near")

In fact, that's how Godot's shader language does it:

vec4 a = vec4(1.0).zxyw;

But, to be fully completionist about this, you would need to add 144 new properties:

All new properties (144) ```csharp struct Vector3 { public Vector2 XY { get; } public Vector2 XZ { get; } public Vector2 YX { get; } public Vector2 YZ { get; } public Vector2 ZX { get; } public Vector2 ZY { get; } public Vector3 XYZ { get; } public Vector3 XZY { get; } public Vector3 YXZ { get; } public Vector3 YZX { get; } public Vector3 ZXY { get; } public Vector3 ZYX { get; } } struct Vector3I { public Vector2I XY { get; } public Vector2I XZ { get; } public Vector2I YX { get; } public Vector2I YZ { get; } public Vector2I ZX { get; } public Vector2I ZY { get; } public Vector3I XYZ { get; } public Vector3I XZY { get; } public Vector3I YXZ { get; } public Vector3I YZX { get; } public Vector3I ZXY { get; } public Vector3I ZYX { get; } } struct Vector4 { public Vector2 XY { get; } public Vector2 XZ { get; } public Vector2 XW { get; } public Vector2 YX { get; } public Vector2 YZ { get; } public Vector2 YW { get; } public Vector2 ZX { get; } public Vector2 ZY { get; } public Vector2 ZW { get; } public Vector2 WX { get; } public Vector2 WY { get; } public Vector2 WZ { get; } public Vector3 XYZ { get; } public Vector3 XZY { get; } public Vector3 XYW { get; } public Vector3 XWY { get; } public Vector3 XZW { get; } public Vector3 XWZ { get; } public Vector3 YZX { get; } public Vector3 YXZ { get; } public Vector3 YZW { get; } public Vector3 YWZ { get; } public Vector3 YWX { get; } public Vector3 YXW { get; } public Vector3 ZXY { get; } public Vector3 ZYX { get; } public Vector3 ZXW { get; } public Vector3 ZWX { get; } public Vector3 ZWY { get; } public Vector3 ZYW { get; } public Vector3 WXY { get; } public Vector3 WYX { get; } public Vector3 WXZ { get; } public Vector3 WZX { get; } public Vector3 WZY { get; } public Vector3 WYZ { get; } public Vector4 XYZW { get; } public Vector4 XYWZ { get; } public Vector4 XZYW { get; } public Vector4 XZWY { get; } public Vector4 XWYZ { get; } public Vector4 XWZY { get; } public Vector4 YXZW { get; } public Vector4 YXWZ { get; } public Vector4 YZXW { get; } public Vector4 YZWX { get; } public Vector4 YWXZ { get; } public Vector4 YWZX { get; } public Vector4 ZXYW { get; } public Vector4 ZXWY { get; } public Vector4 ZYXW { get; } public Vector4 ZYWX { get; } public Vector4 ZWXY { get; } public Vector4 ZWYX { get; } public Vector4 WXYZ { get; } public Vector4 WXZY { get; } public Vector4 WYXZ { get; } public Vector4 WYZX { get; } public Vector4 WZXY { get; } public Vector4 WZYX { get; } } struct Vector4I { public Vector2I XY { get; } public Vector2I XZ { get; } public Vector2I XW { get; } public Vector2I YX { get; } public Vector2I YZ { get; } public Vector2I YW { get; } public Vector2I ZX { get; } public Vector2I ZY { get; } public Vector2I ZW { get; } public Vector2I WX { get; } public Vector2I WY { get; } public Vector2I WZ { get; } public Vector3I XYZ { get; } public Vector3I XZY { get; } public Vector3I XYW { get; } public Vector3I XWY { get; } public Vector3I XZW { get; } public Vector3I XWZ { get; } public Vector3I YZX { get; } public Vector3I YXZ { get; } public Vector3I YZW { get; } public Vector3I YWZ { get; } public Vector3I YWX { get; } public Vector3I YXW { get; } public Vector3I ZXY { get; } public Vector3I ZYX { get; } public Vector3I ZXW { get; } public Vector3I ZWX { get; } public Vector3I ZWY { get; } public Vector3I ZYW { get; } public Vector3I WXY { get; } public Vector3I WYX { get; } public Vector3I WXZ { get; } public Vector3I WZX { get; } public Vector3I WZY { get; } public Vector3I WYZ { get; } public Vector4I XYZW { get; } public Vector4I XYWZ { get; } public Vector4I XZYW { get; } public Vector4I XZWY { get; } public Vector4I XWYZ { get; } public Vector4I XWZY { get; } public Vector4I YXZW { get; } public Vector4I YXWZ { get; } public Vector4I YZXW { get; } public Vector4I YZWX { get; } public Vector4I YWXZ { get; } public Vector4I YWZX { get; } public Vector4I ZXYW { get; } public Vector4I ZXWY { get; } public Vector4I ZYXW { get; } public Vector4I ZYWX { get; } public Vector4I ZWXY { get; } public Vector4I ZWYX { get; } public Vector4I WXYZ { get; } public Vector4I WXZY { get; } public Vector4I WYXZ { get; } public Vector4I WYZX { get; } public Vector4I WZXY { get; } public Vector4I WZYX { get; } } ```
Properties excluding out-of-order or redundant (26) ```csharp struct Vector3 { public Vector2 XY { get; } public Vector2 XZ { get; } public Vector2 YZ { get; } } struct Vector3I { public Vector2I XY { get; } public Vector2I XZ { get; } public Vector2I YZ { get; } } struct Vector4 { public Vector2 XY { get; } public Vector2 XZ { get; } public Vector2 XW { get; } public Vector2 YZ { get; } public Vector2 YW { get; } public Vector2 ZW { get; } public Vector3 XYZ { get; } public Vector3 XYW { get; } public Vector3 XZW { get; } public Vector3 YZW { get; } } struct Vector4I { public Vector2I XY { get; } public Vector2I XZ { get; } public Vector2I XW { get; } public Vector2I YZ { get; } public Vector2I YW { get; } public Vector2I ZW { get; } public Vector3I XYZ { get; } public Vector3I XYW { get; } public Vector3I XZW { get; } public Vector3I YZW { get; } } ```
Joy-less commented 3 weeks ago

I'm going to suggest that these properties are added:

Vector3.xy    # Justification: For side-view platformers and maps directly to Vector2
Vector3.xz    # Justification: For top-down games or comparing horizontally
Vector3.yz    # Justification: For forward-platformers

Vector3I.xy
Vector3I.xz
Vector3I.yz

Implementations as extension methods:

public static Vector2 XY(this Vector3 self) => new(self.X, self.Y);
public static Vector2 XZ(this Vector3 self) => new(self.X, self.Z);
public static Vector2 YZ(this Vector3 self) => new(self.Y, self.Z);

public static Vector2I XY(this Vector3I self) => new(self.X, self.Y);
public static Vector2I XZ(this Vector3I self) => new(self.X, self.Z);
public static Vector2I YZ(this Vector3I self) => new(self.Y, self.Z);
RedMser commented 3 weeks ago

Related: #727

AThousandShips commented 3 weeks ago

Thank you for your proposal, this is already tracked in:

Please continue the discussion there and add your support!