Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Vector casting consistency and safety across platforms

Discussion in 'General Discussion' started by TSiurskas, Jun 15, 2023.

  1. TSiurskas

    TSiurskas

    Joined:
    Apr 7, 2023
    Posts:
    2
    Hey Unity folks!

    Here's the deal: as a Unity developer, I've been casting Vectors like crazy, going back and forth between Vector3 and Vector2. It's been working like a charm, and I'm loving the predictability of it all. But my senior dropped a bomb on me. They're not a fan of vector casting! They claim that although it usually works fine, there's a chance it won't play nice on different platforms. Now, I'm not one to back down without a fight, so I need to know: is this for real?

    To prove my point and give you some context, I've attached a screenshot of my debug logs showing the glorious vector casting operations in action. But seriously, my senior's got me second-guessing, and I need your collective wisdom.

    So, here are my questions to all you Unity gurus out there:

    1. Does vector casting behave differently on different platforms? Are there any cases where casting between Vector3 and Vector2 might just blow up in our faces? Share your technical know-how and any experiences you've had with this!

    2. Can we trust vector casting in general, or should we be exploring alternative approaches? If there are any cool best practices or recommendations for achieving the same outcome, I'm all ears!
    I get that casting might not always be the perfect solution, but it's been working so far. I'm just looking to gather some opinions before I go rewriting everything.

    Thanks a ton for taking the time to help a fellow developer out. I can't wait to hear your thoughts and recommendations!

    upload_2023-6-15_8-7-52.png
     
    CodeSmile likes this.
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,317
    Why don't you ask your seniors what they mean, and ask them to provide an example of a problem it causes?
     
  3. TSiurskas

    TSiurskas

    Joined:
    Apr 7, 2023
    Posts:
    2
    Thanks for your response. I did ask my senior about their concerns, and they mentioned that different platforms might have varying parameter orders for their vectors. For instance, instead of the usual XYZ, it could be XZY on some platforms. This got me thinking that maybe any kind of casting could potentially cause problems.
     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,317
    Meaning your seniors did not explicitly say that casting could cause problems.

    See this example:
    https://docs.unity3d.com/ScriptReference/Vector2-operator_Vector2.html

    You can directly assign Vector3 to Vector2 and vice versa without explicit casting.
     
    Noisecrime and TSiurskas like this.
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,900
    There is no such thing.

    Vector2 and Vector3 are Unity-made struct objects, they are well defined and if anything changes platform by platform (for example potential endianness) Unity (and the underlying C# infrastructure, like Mono) takes care of it, but that's more granular (byte order and whatnot).
    Also Unity provides implicit conversion between VectorN structs, which process also well defined and have nothing to do with platforms.
     
  6. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,833
    That sounds like an Euler Angles issue, not a conversion issue.

    Now, you *could* swizzle .x .y .z into .x .z .y in code if you wanted, but the Unity vector classes are always going to assign the .x from the .x, and the .y from the .y, and the .z from the .z. There's no raw assembly register trickery going on, it's a logical memberwise assignment.

    If you're parsing data from some third-party format, sure, you need to look up what elements in the binary stream belong to what axis (and do conversions if necessary, like dropping a Z coordinate or choosing left-handed or right-handed coordinate systems).
     
    TSiurskas likes this.
  7. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    IMO casting isn't the issue here. Not within Unity anyway. But who knows, next project is Unreal, or Godot, or GameMaker, or a custom engine where endian-ness isn't taken care of. That's where casting MAY fail on some platforms. Within Unity I doubt it.

    The REAL issue is with the interpretation what the x,y,z values represent and that the x,y values of a 3D vector don't necessarily translate to a 2D vector's x,y. They do in your case, but it may fail to work in others thus explicit conversion is recommended over casting.

    There is no natural, unambiguous conversion between a point in 3D space and a point on a 2D plane.

    Specifically, in ProTiler (for now) I use 3D int vectors for the grid but the 2D plane the tilemap is on is the X/Z axis. Thus your casting would fail because:

    Vector2(length, width) != Vector3(length, height, width)

    I actually do this instead:

    Vector2(length, width) != new Vector2(vec3.x, vec3.z)

    The danger lies in getting so used to the casting convention that it will be hard to spot these innocuous issues. Now granted, once you find them it's an easy fix but then it becomes another problem, quite likely for another coder:

    Why is it this in some places:

    Vector2 vec2 = (Vector2)vec3;

    .. and in others it's this?

    Vector2 vec2 = new Vector2(vec3.x, vec3.z);

    Other coders are prone to repeat your mistake. Hence I would stay away from casting and be more explicit.

    Actually, it's quite a shame that we don't have implicit conversion operators and structs like these:


    Size3 size;
    size.width = vec3.x;
    size.height = vec3.y;
    size.length = vec3.z;
    Size3 otherSize = (Size3)vec3;


    This enhances readability and reduces errors as these where one misinterprets and axis value for a thing that it isn't (y != length). But ... as much as I'd like to go in that direction, I am working with Unity.Mathematics types (they are generally faster) and there you have to work with these basic types. You can however, and that's what I do, create aliases (unfortunately one in every file until we get C# 10 support in Unity):

    // at the top next to using statements:
    using GridSize = Unity.Mathematics.int3;

    // somewhere in code
    GridSize size = new GridSize(16, 256, 16);

    Now this still won't give me size.length though, it's still size.z. I wish I could alias field names too.

    I have no idea what it takes to create a custom int3 type that is guaranteed to be Burst compatible too - because this is where most of the speed comes from. Burst understands that a Unity.Mathematics type can be vectorized but I don't know how to make Burst understand that, or maybe it's implicit, I don't know. Well ... maybe I should research that because I'd really love to have my own types for increased expressiveness in my code.
     
    Last edited: Jun 15, 2023
    TSiurskas likes this.
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    Consider this: if there were such cases, then C# and .NET in general would suffer this issue across the board.

    There is nothing magic about Unity's Vector2/3/4 types. They're just a struct with 3 floats, and while I've never had cause to check, I expect that the cast is explicitly defined. If that kind of fundamental operation gave different results on different platforms then it'd be a bug in the runtime environment, and I expect that it'd cause many things to fail.

    All of that aside, I personally avoid implicit cast operations where data may be lost. Not because I'm worried the computer might do something odd, but because I want it to be super clear in my code that only certain data is being used / copied / whatever.
     
    TSiurskas likes this.
  9. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,750
    Your senior seems to have come from a C background and is worried about big endian/little endian issues, where the memory layout of multi-byte types can be different, and reinterpreting a pointer to a type as a different type can give different results.

    However, this is not an issue in this case at all, because that's not how casting works in C#.

    In C#, casting an instance of a custom type "A" to type "B" is just a call to a type conversion function which takes an "A" instance and returns an "B" instance.

    Unity provides functions to convert between Vector3 and Vector2, casting wouldn't be possible otherwise. If there isn't a function to convert between types, you get a compilation error.

    The Vector3 to Vector2 function, as example, creates a new Vector2, copies the X and Y from the incoming Vector3 into it, then returns it.

    So, unlike C casts, there's no reinterpretation of memory contents involved(*) and the behavior is the same everywhere. You get a fresh new Vector2 when you cast from Vector3.

    The only reason to be wary of casts is if you're optimizing a very hot loop because they (along with parameterized constructors and operator overloads) are function calls, which have a small-but-not-zero cost and can make a vector math heavy loop spend as much CPU time copying structs around and pushing/popping the stack as it does doing the actual math (sometimes even more).

    (*) Unless you're using unsafe code and casting between actual pointers, which is possible in C#, but you have to go out of your way to do it and won't happen be accident.