Fix Vector3's method compatibility with IVector3Like

Currently, Vector3 has some strange behavior when compared to other vector and color classes. It is the only vector/color class that uses internal coordinates (_x, _y, _z) and tracks when they are updated. All of its methods then read from the internal coordinates, and external access is done on the getters (x, y, z), which could impact performance.

When it comes to the Tensor API, Vector3 is not compatible with its interface, IVector3Like, but all of the other vector and color classes are. While this is not an issue when working only with Vector3 instances, when working with the interface (IVector3Like), this quickly creates problems for developers as they must implement every static method they use. This highlights that IVector3Like is not really supported, while many of the other I*Like interfaces just work. IVector3Like should just work with a Vector3 class, but due to Vector3’s static methods using the internal coordinate members, IVector3Like is incompatible.

I propose a new class, Vector3Dirty (though any other name would work), which operates on the x, y, and z members of Vector3Dirty and IVector3Like. This class would not have “dirty tracking”, which would speed up performance both for the methods and for users. This class is compatible with Vector3, Vector3Dirty, and IVector3Like, making it a solid choice when a developer does not need dirty tracking but desires compatibility with IVector3Like.

@Deltakosh Requested we talk about this on the forum first.

1 Like

Well I disagree with the idea :slight_smile:
We created the vector3 this way because it is a huge performance boost as it helps the engine invalidate cache and rebuild complex tasks only when needed.

1 Like

That is why I’m proposing a second utility class, which is designed to be used be consumers. Perhaps with a dirty method on Vector3 to get a reference.

I struggle to see the need but I’m open :slight_smile:

The primary use case is for compatibility with IVector3Like, since Vector3 is not compatible with IVector3Like (as the Tensor interface compatibility type parameter, I).

With Vector3Dirty and IVector3Like, one can send IVector3Like in a serialized form, then (without converting it to a Vector3), call Vector3Dirty.Distance(vector3like, actualVector3), which just works.

Vector3.Distance would not work because it uses _x, _y, and _z which do not exist on IVector3Like.

Combined with the toJSON and fromJSON methods I proposed in #15359, this would make communication across JS realms much easier. This includes workers, client/server packets, and data written to files.

I understand how this could seem like it is not needed. In the years I’ve been using Babylon, the biggest struggle has been with transforming data from a serialized to an unserialized form.

Being able to transform a Vector3 into a format easily transferable across realms, and also perform computations using that semi-serialized format would greatly reduce the memory and CPU load of applications since they do not need to instantiate Vector3 to perform sometimes only a single computation.

If you don’t want to add another class, I think changing the Vector3 static methods to use the interface-compatible properties (x/y/z) would also fix this problem.

Please let me know what you think.

not sure to fully understand this ?

Try this:

const vec2a = { x: 1, y: 2 }; // IVector2Like
const vec2b = { x: 3, y: 4 };
Vector2.Distance(vec2a, vec2b); // 2.82...

const vec3a = { x: 1, y: 2, z: 3 }; // IVector3Like
const vec3b = { x: 3, y: 4, z: 5 };
Vector3.Distance(vec3a, vec3b); // NaN?!

What I struggle with is: assuming we have a Vector3Dirty, This will not be used by the framework right? So what is the goal of these classes within babylon?

It’s for use by the users, and in case any part of the framework doesn’t need dirty tracking.

Realistically, does every Vector3 instance in the framework need dirty tracking?

No but the thing is that all main properties will be Vector3 anyway, so you expect users to cast? convert? I do not see how as a user getting my scene, my meshes and everything based on vector3 properties,how I will use a vector3Dirty

Most end users do not care if their positions have dirty tracking or not.

Perhaps introducing Vector3Dirty isn’t even needed.

What I’m trying to propose is making Vector3’s methods compatible with IVector3Like, so that users don’t need to create a new Vector3 to add some, for example. The idea is that this code would just work: Vector3.Distance({ x: 1, y: 2, z: 3 }, { x: 4, y: 5, z: 6 }), which was broken when dirty tracking was introduced in v4.2.

Edit:
For clarification, creating an IVector3Like compatible class was my original approach, since I didn’t want to degrade Vector3’s performance.

1 Like

@Deltakosh,

It seems like you’re against Vector3Dirty. Do you have any idea on how to go about reintroducing the broken compatibility?

I’m against because I see no use case that cannot be solved by simply creating or reusing a vector3.

I do not want to bloat the framework when we are trying super hard to make it as slim as possible

1 Like

One example I’d like to throw out is with client/server stuff for a game.

If the server sends a packet to the client every frame, which has velocity vectors for every entity in a world, which is then added to the entities’ positions in the client, we have this situation

server: creates a new Vector3 for every entity every frame, which is immediately disposed of after being sent to the client

client: creates a new Vector3 for every entity every frame, which is immediately disposed of after being added to the entities’ position

This means, if there are 1000 entities (which is not unreasonable considering things like projectiles or other small objects), we are creating 60,000 Vector3s per second, which are immediately disposed of.

This makes no sense to me when instead of instantiating, serializing, deserializing, and instantiating a Vector3, we could instead just have the client position.add(serializedVelocity), (which as I’ve explained does not work due to Vector3’s method incompatibility with IVector3Like). In this case, the server doesn’t even need to use Vector3s, unless it needs to add some together.

I fully understand the performance improvements made by dirty tracking, but I think we also need some compatibility and consistency with Vector3’s methods.

​
​
I’m also all for sliming the framework. One idea I’ve had is consolidating the variations of operations on the math classes, so for example

add(other);
addToRef(other, ref);
addInPlace(other);

could just be

add(other, ref = new this())

Obviously we would maintain the old methods for a until the next breaking release, and this is just an idea.

​​​
​
​
How do you think we can add some compatibility?

You can reuse a vector3:) no need to create a new one.
Anyway! This is a feature request. You know my opinion but if community is voting for it then I’ll change my mind

3 Likes

@Deltakosh , i dont know if you missed this part of the conversation? Outside of what you think the OP is asking for - This is a valid inconsitency being pointed out here right? ( there is a link with a PG as well to show it )

Vector2.Distance - accepts plain objects as arguments and works
Vector3.Distance - does not accept plain objects as arguments

1 Like

I saw it. It was done for perf reason as I mentioned earlier.

And again:

const vec3a = Math.TmpVector3[0].set(0, 1, 2);
const vec3b = Math.TmpVector3[1].set(3, 4, 5);
Vector3.Distance(vec3a, vec3b)

This is better because no GC and it covers the needs of OP.

And please do not get my wrong: I’m not immune to the interest of consistency but ultimately Babylon has to be performant. Being pleasant to the developer eye is on second in the priority list

2 Likes

Thanks for explaining :wink: , no new allocations is indeed more important.

1 Like

Thank you for the astute observation @shaderbytes.

This creates multiple race conditions since the value of Math.TmpVector3[N] could be changed by the engine before the computation is performed. This is because of the event-based nature of JS.

Context

With that out of the way, I’d like to give some context I think is very important, and will help clarify both my use case and potentially that of others.

I’m working on a game, that has 4 major components:

  • Core: Mostly data, classes like Entity, Level, and handling their serialization into JSON
  • Server: Think Minecraft server, it works kind of like that
  • Client: GUI, saves, servers, etc.
  • Renderer: Renders things

Importantly, the other 3 parts rely on the core. I’m trying to see how feasible it is to eliminate Babylon as a runtime dependency for the core, which involves using plain objects instead of vector and color class instances. This has been part of why I’ve been pushing for compatibility.

Solutions

I think we definitely need something for consistency and compatibility, but I’m not sure what:

Do we add a new class, like I originally proposed with Vector3Dirty?

  • As @Deltakosh has mentioned, adding a new class increases Babylon’s size and might not be worth it.

Do we add methods to Vector3 (e.g. addDirty)?

  • This isn’t compatible with IVector3Like in a way that is Tensor-compliant.

Do we modify Vector3’s methods to use the normal properties (e.g. x) instead of the internal ones (e.g. _x)?

  • Well no, we can’t do that because of the performance hit

Do we add some new functions (e.g. add), which can handle it?

  • I think this has potential since they can be tree-shaken, and could potentially be dimension-agnostic.
  • Unfortunately, this adds something to the engine, and may not be worth the size increase.

Do we change the other vector and color classes to behave the same way, for consistency?

  • Maybe, though then we are actively losing functionality with very little gain.
  • Since the change for Vector3 wasn’t considered a breaking change, this wouldn’t be either
  • An aside: Right now the difference between Vector4 and Quaternion is minimal, and mostly because Quaternion performs dirty tracking while Vector4 does not, in addition to a few extra methods. It may be worthwhile to look into changing Vector4 to perform dirty tracking, and then add the missing methods to it, essentially making Quaternion just an alias, which would lead to a pretty sizable decrease in package size.

Do we change the interface type parameter for Vector3 from Vector3 to an interface with the internal properties (e.g. _x)?

  • I think this is a plausible approach that would help solve the problem, since users could, for example, do myVector3.add({ _x: 1, _y: 2, _z: 3 }).
  • This solution has the least impact.
    • It has no change to performance
    • It has no change to runtime code
    • It only makes some types broader, and only introduces a single new interface

If none of the above solutions work, what do you think we can do?