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

Add Vector4 #59970

Closed
wants to merge 2 commits into from
Closed

Add Vector4 #59970

wants to merge 2 commits into from

Conversation

Geometror
Copy link
Member

@Geometror Geometror commented Apr 7, 2022

This PR adds Vector4 as a new variant type and streamlines the usage of 4D-Vectors across the engine. There are a lot of possible use cases for a general 4-tuple where Plane, Color or Quaternion feel hacky because they all have specific constraints and should be used in a specific context. See godotengine/godot-proposals#629 (comment) for a good summary on this problem. Also, new users may find themselves confused, not knowing which type can be used for a general vector with 4 components since many other game engines have a general Vector4 type. Sure, this adds a lot of (boilerplate) code, but having a Vector4 makes things much cleaner in my opinion.

Closes godotengine/godot-proposals#629
Closes godotengine/godot-proposals#3761
Implements godotengine/godot-proposals#258 partly

Changes in detail:

  • Add Vector4 as a new variant type, which can be used everywhere in the engine (GDExtension/Mono/...)
    • implemented methods: abs, bounce, ceil, clamp, cubic_interpolate, direction_to, distance_squared_to, distance_to, dot, floor, inverse, is_equal_approx, is_normalized, length, length_squared, lerp, limit_length, max_axis_index, min_axis_index, move_toward, normalized, posmod, posmodv, project, reflect, round, sign, snapped (the operators are the same as for Vector3)
    • new Vector4 EditorIcon (room for improvement, I couldn't find out which font is used in these icons)
    • For vec4 shader uniforms, Vector4 is now used instead of Plane
  • Add basic Vector4 test suite (coverage similar to Vector3)
  • Add Vector4 to Visual Shader
    • Implement conversion between primitive types
    • Added new Vec4Uniform and Vec4Constant nodes
    • Added Vector4 support to all vector nodes
    • While at it: Fix incorrect conversion from vec2 to int
  • Add Vector4 to Visual Script

This PR could be split up if desired (add Vector4 variant type | (visual) shader integration).
Since this introduces a new variant type which is deeply integrated into the engine, a thorough review and extensive testing may be required.

grafik

Optional/still considering:

  • add some convenience constructors (maybe Vector3+float, Vector2+Vector2) and conversion methods
  • change in Vector4 methods: keep the bounce method?/add cross() (not well-defined for 4d)?
  • add PackedVector4Array?
  • add Vector4i? (probably not needed)

Feedback is, as always, much appreciated :)

@Geometror Geometror requested review from a team as code owners April 7, 2022 02:35
@Chaosus
Copy link
Member

Chaosus commented Apr 7, 2022

Oh, that's cool. I would also go through all Color/Alpha ports/inputs in visual shader and unify them into single vec4.

@aaronfranke
Copy link
Member

@Chaosus Shouldn't Color ports be using Color, not Vector4?

xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can be minified with svgcleaner (plus ensure there's a newline at the end).

// Constants
private static readonly Vector4 _zero = new Vector4(0, 0, 0, 0);
private static readonly Vector4 _one = new Vector4(1, 1, 1, 1);
private static readonly Vector4 _inf = new Vector4(Mathf.Inf, Mathf.Inf, Mathf.Inf, Mathf.Inf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's likely worth adding some constants for the basic unit vectors. It's not sensible to give friendly names like "up" and "right", but we can still declare some constants.

        private static readonly Vector4 _unitX = new Vector4(1, 0, 0, 0);
        private static readonly Vector4 _unitY = new Vector4(0, 1, 0, 0);
        private static readonly Vector4 _unitZ = new Vector4(0, 0, 1, 0);
        private static readonly Vector4 _unitW = new Vector4(0, 0, 0, 1);

{ Variant::TRANSFORM2D, 6 * sizeof(float), 6 * sizeof(float), 6 * sizeof(double), 6 * sizeof(double) },
{ Variant::PLANE, (vec3_elems + 1) * sizeof(float), (vec3_elems + 1) * sizeof(float), (vec3_elems + 1) * sizeof(double), (vec3_elems + 1) * sizeof(double) },
{ Variant::PLANE, vec4_elems * sizeof(float), vec4_elems * sizeof(float), vec4_elems * sizeof(double), vec4_elems * sizeof(double) },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't make sense semantically. A plane data structure is composed of a normal vector (for 3D, that's a Vector3), and the distance from the origin.

I think that the entire purpose of vec3_elems is to make the code more semantic, since Vector3 is used inside of many other structures, so this change doesn't make much sense. Also, since Vector4 isn't used inside of other structures, it probably doesn't make much sense to define vec4_elems, since we don't have rect2_elems or similar.

@Chaosus
Copy link
Member

Chaosus commented Apr 7, 2022

@Chaosus Shouldn't Color ports be using Color, not Vector4?

Maybe, at first glance, shaders does not have "color" type, it's syntax sugar over vec4 type.

Comment on lines 198 to 201
v.x = f->get_32();
v.y = f->get_32();
v.z = f->get_32();
v.w = f->get_32();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
v.x = f->get_32();
v.y = f->get_32();
v.z = f->get_32();
v.w = f->get_32();
v.x = f->get_real();
v.y = f->get_real();
v.z = f->get_real();
v.w = f->get_real();

core/io/resource_format_binary.cpp Outdated Show resolved Hide resolved
@groud
Copy link
Member

groud commented Apr 7, 2022

As I mentioned on the proposal, I am not convinced by the usefulness of this. The only situation I think they might be really useful are Quaternions, for which an explicit type makes more sense. For other places like @aaronfranke suggested, I think keeping dedicated types for Color, Plane or Rect2 is better and makes a lot more sense than using a generic Vector4 type. For example, there's no reason why you would add two Rect2 represented as Vector4, as it would basically mean adding the position and the size together, this seems like a quite useless thing to do.

I general, for future PRs, I would suggest you to ask maintainers (for core things like that, likely reduz) for an approval. Otherwise all of this might end up in a lot of wasted work.

@Geometror
Copy link
Member Author

@groud Sorry, I am not sure if I understood you correcly. My intention here is not to replace these specialized types but to supplement them by a general 4D vector which can be used in all cases where Plane, Quaternion, Rect2 and Color aren’t suitable. In my opinion, using Plane for a tuple which does not represent a plane at all feels hacky.
Regarding Visual Shader: Adding all these specialized 4-tuples to the shader would make things very complicated and bloated.

Some use cases in which Plane/Quaternion/Rect2/Color make no sense or aren’t fitted (as @aaronfranke stated in godotengine/godot-proposals#629):

Looking at the amount of upvotes godotengine/godot-proposals#629 got, this seems to be a feature which many users miss in Godot.
But you’re right, I should have asked reduz or another core maintainer before implementing, as adding a new variant type is a rather substantial addition. Initially, this was planned as a small demo PR for the proposal, but I underestimated the amount of work required for such an addition, yet I kept going. However, even if this PR won’t be merged, I wouldn’t see this as wasted work since I learned a lot about the internal structure of the engine :)
[or it might become useful as reference for adding new variant types]

@groud
Copy link
Member

groud commented Apr 7, 2022

The problem I see with most of those use cases, is that none of them benefit from what a Vector4 would provide. The point of creating a new type is mainly to allow manipulating them as a single element, where addition, multiplication, or any other operation generic to a Vector would make sense. In most of you examples, namely:

  • Axis-Angle 3D rotation
  • 3D vectors in Direction-Magnitude
  • 4D noise offsets (3D noise + time)

In all those examples, there's always a 4th component that needs to be manipulated independently of the other ones. Which makes a Vector4 type a lot less interesting, as you will need a lot of helper functions to manipulate them according to the underlying type of object they represent anyway.

In the end, the only advantage they provide is that, in some cases, they would be more efficient than returning a dict when used as a generic container for four float values.
But if we go that route, it will never stop, some users will end up wanting a Vector5, a Vector6, and so on... just because it's easier/more efficient to manipulate a single Variant value than to use a Dictionary or create a dedicated object.

In my opinion, this highlights the fact that we instead kind of need a better support of tuples. Or maybe make users aware it is simply advised to create a dedicated class for their use case, when they need it, to represent a set of packed values.

@reduz
Copy link
Member

reduz commented Apr 7, 2022

I agree with @groud, the use for this is too limited, and Variant is a very core type, for which adding something to it has to be warranted as really being used a lot.

@aaronfranke
Copy link
Member

@Geometror I just want to say that this isn't wasted work, it's useful to me because it shows a minimal example of how to add a new type to Variant. But yeah, this really needed approval from reduz beforehand if your plan was to have it merged.

@Geometror
Copy link
Member Author

@aaronfranke As I stated in my last comment, I wouldn't see this as wasted work too :)
Maybe the VisualShader part can be salvaged, since that works without adding a new variant type (using Plane or Quaternion) and should have been in a separate PR to begin with. Along the way I also found a few bugs, which I will try to fix in some future PRs.

@name-here
Copy link

If Vector4 is not needed, why do we have vec4 in the shader language? If nothing else, it would be great to have an equivalent type in GDScript that can be used when interacting with shaders. Having to figure out which of the existing types to misuse when passing values seems like an unnecessary annoyance.

@jordo
Copy link
Contributor

jordo commented Apr 7, 2022

But if we go that route, it will never stop, some users will end up wanting a Vector5, a Vector6, and so on... just because it's easier/more efficient to manipulate a single Variant value than to use a Dictionary or create a dedicated object.

I think it's a pretty bad argument to say where does godot draw the line if godot accepts a vector4 struct in Variant. It's pretty clear that the line would be drawn at vector4, as that's exactly where it's drawn in almost every other game engine. Unity and unreal, et al. As well, moving beyond the internal size of Variant's _data (4 float) is going to be ill performant for this usage, so I think it's pretty clear it would not move past Vector4.

Odd there is aversion to this, when Plane, Quaternion, Rect2 and Color are already handled specialized Variant 4-float types when the more generalized form from my perspective would be utilized the most. Especially wrt mapping type to shader uniform type for example as mentioned above. Anecdotally we use Color right now for this, which is so bizzare because I have to keep reminding myself that all our 4-dimentional vectors are colors because there is no Vector4 type.

@jordo
Copy link
Contributor

jordo commented Apr 7, 2022

For reference:

https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector4/
https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Utilities/Struct/MakeVector4/
https://docs.unity3d.com/ScriptReference/Vector4.html
https://docs.flaxengine.com/api/FlaxEngine.Vector4.html
https://docs.cocos2d-x.org/api-ref/cplusplus/V3.9/d2/dad/classcocos2d_1_1_vec4.html
https://defold.com/ref/vmath/#vmath.vector4:x-y-z-w
https://docs.coregames.com/api/vector4/
https://doc.stride3d.net/4.0/en/api/Stride.Core.Mathematics.Vector4.html

I'm actually having a hard time finding any other engine that DOESN'T expose a native Vector4 type... Are there even any? I feel like this probably isn't a good one for Godot to be the 'standout'.

@Schroedi
Copy link
Contributor

Schroedi commented Apr 7, 2022

The first time I found out that GD Script doesn't have a vec4 I was also very puzzled.
One of my use cases is to test and visualize code from research papers or my own ideas. Admittingly, I also miss 4x4 Matrices there. Currently the only reasonable way around it which I am aware of is GD native. This however, defeats the fast iteration time I choose Godot for in the first place.

@clayjohn
Copy link
Member

clayjohn commented Apr 7, 2022

It is great to see so much interest in this. There has also been some discussion on the RocketChat #core channel which I aim to summarize below.

The largest consideration here is the cost of adding another type to Variant and thus to the "core" of Godot. We really try to avoid adding anything to core if we can. This is even stated in our best practices for contributors document.

If we are going to add something to core, then we need to ensure that it is both necessary and the cost is minimized. As time goes on and the usage of Godot increases. It becomes very hard to show that something is "necessary" as other developers will just say "not having it hasn't stopped anyone yet".

All that is to say that the threshold to merge this is very high. Much higher than for an equivalent change to scene or editor.

On the other hand, between this PR and the proposal, we haven't had a chance to fully develop the areas which absolutely need the introduction of a Vector4 class. @aaronfranke has helpfully started us off with the proposal, which lists some use-cases godotengine/godot-proposals#629. and @Geometror has refined that list in this comment (reproduced below):

In the conversation, significant doubt has been shed on the necessity of these use-cases.

I will go through them one-by-one here. Also, if anyone else has other use-cases, please post them as well.

Working with Shaders
This is where I see the biggest need, it is a major pain point for many users to cast values to a Rect2 or a Plane so that they can set the value of a vec4. If Rect2 or Plane change in the future (for example to restrict negative numbers) it may render them useless.

At the same time, this is more of a cosmetic thing, we could also provide an overload for setting vec4s, something like set_shader_param(String name, float x, float y, float z, float w).

It would definitely be more convenient to just use a Vector4 from GDscript when working with vec4s in shaders.

Representing Axis-angle 3D rotation
Personally, I have not seen a demand or need for this. Please let me know if there is either.

Representing 3D vectors in Direction-Magnitude format
This sounds just like the current Plane to me

4D noise offsets
Same issue as with shaders. It would be handy if we could do something like get_noise_4d(pos + offset) where pos and offset are Vector4s.

For general linear algebra calculations
Godot doesn't expose a general 4x4 Matrix, so I am not sure there is much value in a Vector4 class for linear algebra. If we add Vector4, we should strongly consider adding Matrix4. But this of course would add even more weight to variant and to the core.

Simply giving the freedom to store 4 values
Unfortunately, we don't consider things like this a strong use-case. We need something more specific.

All the other engines expose it
On its own, this isn't a consideration. But, if we look deeper we may be able to see why other engines all expose it. What we need is the why. It would be very helpful if someone wants to do a little investigation and explain why all other engines support a native Vector4 class.

Conclusion
Please, anyone, feel free to comment with your specific use-case where you are finding that you need a native Vector4 class. At this time, it is looking like the known benefits may not outweigh the known cost (remember, there is a very high threshold to get something into core), but there is always room for more information and the engine developers need to hear from users with respect to what is needed. So again, please, if you have a use-case not listed here, please describe it in detail!

@fire
Copy link
Member

fire commented Apr 7, 2022

For bone indicies and bone weights in Godot Engine 4 they can be 4 or 8 floats. In other engines they may be 32 or greater per vertex. I prefer the current structure of using float arrays.

Edited: wrong attribute.

@@ -480,13 +480,20 @@ _FORCE_INLINE_ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataTy
gui[1] = v.y;
gui[2] = v.z;
gui[3] = v.w;
} else {
} else if (value.get_type() == Variant::PLANE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the topic of misc fixes, I think it's likely better that this check this should be here anyway, and the final else would be an error.

} break;
case VisualShaderNode::PORT_TYPE_VECTOR_3D: {
inputs[i] = "dot(" + src_var + ", vec3(0.333333, 0.333333, 0.333333))";
} break;
case VisualShaderNode::PORT_TYPE_VECTOR_4D: {
inputs[i] = "dot(" + src_var + ", vec3(0.25, 0.25, 0.25, 0.25))";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inputs[i] = "dot(" + src_var + ", vec3(0.25, 0.25, 0.25, 0.25))";
inputs[i] = "dot(" + src_var + ", vec4(0.25, 0.25, 0.25, 0.25))";

} break;
case VisualShaderNode::PORT_TYPE_VECTOR_3D: {
inputs[i] = "dot(float(" + src_var + "), vec3(0.333333, 0.333333, 0.333333))";
} break;
case VisualShaderNode::PORT_TYPE_VECTOR_4D: {
inputs[i] = "dot(float(" + src_var + "), vec3(0.25, 0.25, 0.25, 0.25))";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inputs[i] = "dot(float(" + src_var + "), vec3(0.25, 0.25, 0.25, 0.25))";
inputs[i] = "dot(float(" + src_var + "), vec4(0.25, 0.25, 0.25, 0.25))";

@jordo
Copy link
Contributor

jordo commented Apr 8, 2022

The existing 4-float types in godot's Variant Plane, Color, Rect2, Quaternion all are specialties of a general form, and have special handling in a bunch of different scenarios and areas in the engine.

A vec4 is a common structure in game development as it's generally understood as no-restriction. I guess my specific use-case is that it's very un-ergonomic to work with packed Colors for this data then convert to Plane's for the cases I need to bind to a shader vec4 uniform param, etc. (#28504). The linked issue is perfect example of special case handling, and how it introduces issues and one-off problems.

I believe the engine should strive for consistency and intuitiveness. Shading language vec4 should be able to be easily used with same structure cpu side set_shader_param("blah", Vector4()), for clarity and easy of use. Regardless of whether w, or any parameter needs special treatment or not.

core/math/vector4.h Outdated Show resolved Hide resolved
Comment on lines 153 to 164
// Multiplication operators required to workaround issues with LLVM using implicit conversion
// to Vector3i instead for integers where it should not.

_FORCE_INLINE_ Vector4 operator*(const float p_scalar, const Vector4 &p_vec) {
return p_vec * p_scalar;
}

_FORCE_INLINE_ Vector4 operator*(const double p_scalar, const Vector4 &p_vec) {
return p_vec * p_scalar;
}

_FORCE_INLINE_ Vector4 operator*(const int32_t p_scalar, const Vector4 &p_vec) {
return p_vec * p_scalar;
}

_FORCE_INLINE_ Vector4 operator*(const int64_t p_scalar, const Vector4 &p_vec) {
return p_vec * p_scalar;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's no Vector4i, these probably aren't necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to remove it, but it wouldn't compile. I have a feeling that this comment might be outdated since it seems like these operators are needed regardless of having an integer variant of Vector4.

@Zireael07
Copy link
Contributor

My use cases: shaders and noise AND this: godotengine/godot-proposals#4311

Quite possible vector4 would make it even easier to do than the current hacking around with planes and transforms and whatnots

@Geometror Geometror force-pushed the add-vector4 branch 2 times, most recently from ddf50af to fd32876 Compare April 8, 2022 11:35
// return step > 0;
// }
// return step < 0;
// } break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind adding this commented-out code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this was by mistake. I initially planned to implement the iter_* functions for Vector4, but I was not sure how to do it correctly, so I wanted to wait whether this PR would be approved first.

core/math/vector4.h Outdated Show resolved Hide resolved
core/math/vector4.h Outdated Show resolved Hide resolved
@GlitchedCode
Copy link

Since Godot's shading language defines ivec4 and uvec4, i'd like to suggest you reconsider your stance on Vector4i, since it would allow for more straight-forward usage of integer vector uniforms in code. Not that much of an high priority feature, but it would definitely help with compute shaders, especially considering they will be officially supported in 4.0.

@YuriSizov
Copy link
Contributor

Can I pile on this too? 🙃 There is a clear evidence in Godot's own API that we need a Vector4. Here's a method of MultiMesh that uses Color as a substitute for a Vector4 value, and it does the same double-take in its class reference as @jordo has to do in their code.

https://docs.godotengine.org/en/stable/classes/class_multimesh.html#class-multimesh-method-set-instance-custom-data

void set_instance_custom_data ( int instance, Color custom_data )

Sets custom data for a specific instance. Although Color is used, it is just a container for 4 floating point numbers. The format of the number can change depending on the CustomDataFormat used.

@Geometror
Copy link
Member Author

I have looked through few other forums (e.g., Unity) and it seems like the amount of people using Vector4 for 4D math is actually not that low. It can be difficult to compile a list of concrete use cases since often those who use it in their code are working on closed-source games/projects. Besides that, we have the aspect of consistency (again, using a Quaternion, Rect, Plane or Color instead of a 4D vector in places they don't make sense feels hacky, especially when all of these types are used promiscuously)
In addition, @lawnjelly made a good point regarding SIMD-optimizations (see godotengine/godot-proposals#629 (comment)). In that regard, a PackedVector4Array would make sense too (maybe replacing PackedColorArray).

I can understand that adding new types to the core must be a deliberate decision, but I genuinely believe that in this case the whole codebase would benefit from it.

Given the amount of upvotes the original proposal and this PR got, I think we should reconsider adding a general Vector4 type to core. I agree with @jordo, there has to be a reason why nearly all engines have a general 4D vector type.

I gladly rebase/adjust my PR if a final decision is made. Maybe this can be discussed in the next PR meeting?

If we really don't want an additional variant type there was also this[https://github.com/godotengine/godot-proposals/issues/629#issuecomment-1092619812] suggestion to replace Plane with Vector4 which is - although not optimal - still better than the other way around, using Plane as a 4D vector, in my opinion.

@reduz
Copy link
Member

reduz commented Jun 21, 2022

Here's the result of the discussion on PR review:

  • Not enough convincing evidence to have this type
  • Instead, we can just add an alias of Vector4 to Quaternion, which is entirely compatible with Vector4 (same operators, normalization, etc plus the quaternion functions). This way you can pass things as Vector4().
  • If alias is used, we can add a property hint that treats Quaternion as Vector4 in the inspector, like we have for other types (treating vector2 as size and so on).

@akien-mga
Copy link
Member

Superseded by #63219.

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

Successfully merging this pull request may close these issues.

VisualShader add Vector2 & Vector4 input/output node connections Add Vector4 to the engine