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

Standardize Scalar, Vector2, Vector3, Vector4, Color3, Color4, Quaternion, Matrix #14201

Closed
james-pre opened this issue Aug 24, 2023 · 5 comments · Fixed by #14235
Closed

Standardize Scalar, Vector2, Vector3, Vector4, Color3, Color4, Quaternion, Matrix #14201

james-pre opened this issue Aug 24, 2023 · 5 comments · Fixed by #14235
Assignees
Milestone

Comments

@james-pre
Copy link
Contributor

james-pre commented Aug 24, 2023

This proposal is also available on the Babylon.js forum in this thread.

TL;DR:

Tensor would be added, which provides a standardized API for interacting with vectors, colors, matrices, quaternions, etc.

Rational

This proposal's goals are based on the ideas behind #13699, with the core idea being to create a standard interface from which various math constructs are defined.

Take the current difference in behavior between vectors and colors:

const c1 = Color3.Random(),
	c2 = Color3.Random(),
	c3 = new Color3(),
	v1 = Vector3.Random(),
	v2 = Vector3.Random(),
	v3 = new Vector3();

c1.addToRef(c2, c3) === c3 // false
v1.addToRef(v2, v3) === v3 // true

And the lack of a method that should exist in Color3:

const v1 = Vector3.Random(),
	v2 = Vector3.Random(),
	c1 = Color3.Random(),
	c2 = Color3.Random();


v1.addInPlace(v2) // ok
c1.addInPlace(c2) // ReferenceError! addInPlace does not exist

Tensor provides a single interface for tensor-like objects which users can depend on. By having tensor-like classes implement Tensor, it ensures that all the methods exist and have the same and correct behavior. It is meant for organization and standardization.

Semantics

Note:

  • CurrentClass is a placeholder for a class that implements Tensor. In fields, it is the current class.
  • Tensor and its related types will be defined in src/Maths/tensor.ts unless otherwise noted

Vector (from #13699 )

Defined in src/Maths/math.vector.ts

This proposal also includes Vector from #13699. Vector extends Tensor and adds normalization methods and length / lengthSquared. It is implemented by Vector2, Vector3, and Vector4.

MultidimensionalArray

Defined in src/types.ts

type MultidimensionalArray<T, D extends number> = D extends 0
	? T
	: MultidimensionalArray<T, Decrement<D>>[];

Represents a multidimensional array of T with depth D.

TensorValue

type TensorValue = MultidimensionalArray<number, number>

Tensor

declare class Tensor<V extends TensorValue>

Tensor is a declare class for tensor-like classes to implement.

Tensor includes all of the methods in VectorLike. This includes

  • Math operations (add, subtract, multiply, divide, scale, ...)
  • Array conversion (fromArray, toArray, asArray, ...)
    • Since the type parameter is no longer assignable to number[], the return type for array-related methods will be changed to number[]
    • See Tensor.value
  • Transfering (clone, copyFrom, copyFromFloats, ...)
  • The above methods' InPlace, ToRef, FromFloats, etc.

It adds or changes the following methods:

Tensor.From

public static From(source: Tensor, fillValue: number = 0): CurrentClass;

From creates a new instance of CurrentClass from source.
source: The tensor to copy data from.
fillValue: The value to use for filling empty parts of the resulting CurrentClass.

Example:

const vec3 = new Vector3(1, 2, 3);
const vec4 = Vector4.From(vec3, 4); // { 1, 2, 3, 4 }
const matrix = Matrix.From(vec3, 0); // this is possible!

Additionally, From could be changed to be From(...args: [...Tensor[], number]): CurrentClass. This is better understood by this invalid Typescript signature (since rest parameters must be last):

public static From(...source: Tensor[], fillValue: number = 0): CurrentClass;

Tensor.as

public as<T extends typeof Tensor>(type: T, fillValue: number = 0): InstanceType<T>;

as creates an instance of type from an instance of CurrentClass.
type: The class to create an instance of.
fillValue: The value to use for filling empty parts of the resulting instance.

Example:

const vec3 = new Vector3(1, 2, 3);
const vec4 = vec3.as(Vector4, 4); // { 1, 2, 3, 4 }
const matrix = vec3.as(Matrix, 0);

Tensor.sum

public sum(): number;

sum return the sum of the components of the Tensor.

Example:

const scalar = new Scalar(1), 
	vec3 = new Vector3(1, 2, 3),
	vec4 = new Vector4(4, 5, 6, 7),
	matrix = new Matrix();

scalar.sum() // 1
scalar.sum() // 6
scalar.sum() // 22
scalar.sum() // 0

⚠ The return value of the lengthSquared or length method of vectors is not the same as the return value of sum.

Tensor.rank

public abstract rank: number;

The rank of a tensor is the number of indices required to uniquely select each element of the tensor.

🛈 The rank of a Tensor is the same as the length of its dimension.

Example:

const scalar = new Scalar(), 
	vec3 = new Vector3(),
	matrix = new Matrix();

scalar.rank // 0, since indicies are not needed
vec3.rank // 1
matrix.rank // 2

Tensor.dimension

public abstract dimension: number[];

dimension is the mathematical dimension [2] of the tensor. In dynamic tensor types, it can be defined as a getter.

Example:

const scalar = new Scalar(),
	vec3 = new Vector3(),
	vec4 = new Vector4(),
	matrix = new Matrix();

scalar.dimension // []
vec3.dimension // [3]
vec4.dimension // [4]
matrix.dimension // [4, 4]

Tensor.value

public get value(): V;
public set value(value: V): void;

value is the values of the tensor in a multidimensional array with the type V (the type parameter of the class). Unlike dimension, this must be implemented as a getter and setter.

Example:

const vec3 = new Vector3(),
	matrix = new Matrix();

vec3.value
// [0, 0, 0]

matrix.value
// [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ]

vec3.value = [ 1, 2, 3 ];
vec3.x // 1

isTensor

function isTensor(value: unknown): value is Tensor;

Checks if a value is a Tensor.
Example:

const vec3 = new Vector3();
isTensor(vec3) // true
isTensor(vec3.value) // false
isTensor([1, 2, 3]) // false
isTensor(0) // false
isTensor(null) // false

isTensorValue

function isTensorValue(value: unknown): value is TensorValue;

Checks if a value is a TensorValue.
Example:

const vec3 = new Vector3();
isTensorValue(vec3) // false
isTensorValue(vec3.value) // true
isTensorValue([1, 2, 3]) // true
isTensorValue(0) // true
isTensorValue(null) // false

getDimension

function getDimension(value: Tensor | TensorValue): number[];

getDimension returns the mathimatical dimension [2] of value, similar to Tensor.dimension. If value is not a Tensor or TensorValue, it will throw a TypeError

🛈 Tensor based classes can use getDimension(this.value) for their dimension implementations.

Considerations

  1. Static methods related to data manipulation (e.g. Add, Normalize, Lerp) and non-static normalization methods are outside the scope of this proposal. While Tensor may include them in the future, this proposal does not
  2. This proposal does not provide for a dimensionally dynamic Tensor.
  3. Performance: Since Tensor is defined using the declare class and classes that follow Tensor do so using the implements keyword, there are no runtime changes to Tensor-based classes.
  4. Bundle size: The JS bundle size will increase only by the size of the added functions (isTensor, getDimension, etc.). The TS declaration size will also increase slightly due to Tensor.
  5. Maintenance: Since there is no implementation (just a class declaration), the extra maintenance is minimized to member signatures.

Questions

  1. Should Scalar be included in this proposal? As a rank 0 tensor, the benefits of standardizing Scalar may not be worth it, especially considering it is not currently meant to be instantiated.
  2. How should incompatibility be managed? Should incompatible classes be dropped or should they be modified to follow Tensor?
  3. What other classes (e.g. Size) would be included and standardized?

FAQs

What use cases does this proposal have?

Tensor should not be used by end users. It is meant for organization and standardization. The functions (isTensor, isTensorValue, getDimension, etc.) may be used by the end user. isTensor and isTensorValue can be used for improved type safety, type checking at runtime. getDimension can be used for iteration and for getting information.

What is the difference between this and #13699?

The capabilities of the type. VectorLike supports any rank 1 tensor. Tensor generalizes VectorLike, and support any rank tensor. A mathematical vector can be represented as number[]. A mathematical tensor can be represented as a number, number[], number[][], and so on. This table highlights the capability:

tensor rank TS representation BJS class (if exists)
0 number Scalar
1 number[] Vector, DynamicVector
1 [number, number] Vector2
1 [number, number, number] Vector3, Color3
1 [number, number, number, number] Vector4, Color4, Quaternion
2 number[][] Matrix
3 number[][][]
4 number[][][][]
N TensorValue

Further reading

  1. https://en.wikipedia.org/wiki/Tensor
  2. https://en.wikipedia.org/wiki/Dimension
@thomlucc thomlucc added this to the 7.0 milestone Aug 24, 2023
@james-pre
Copy link
Contributor Author

james-pre commented Aug 29, 2023

I'm closing #13699 since having it as well as this would create some conflicts. I will include Vector (from #13699) in the PR for this issue. In addition, I'm including some new types for compile-time math (which is needed for MultidimensionalArray). I've made edits to my initial comment with the proposal.

@james-pre
Copy link
Contributor Author

@thomlucc Could you please move this back to the 7.0 milestone? Me and the BJS team are trying to get it merged for the big 7.0 release, and it would be nice if the issue reflected the milestone correctly. Thanks!

@sebavan
Copy link
Member

sebavan commented Mar 4, 2024

We are in code freeze and we will only merge it after 7.0, this is a large change and has some undeniable risks attached.

@james-pre
Copy link
Contributor Author

@sebavan I thought we were going to merge it with 7.0 since it involves some breaking fixes?

@sebavan
Copy link
Member

sebavan commented Mar 4, 2024

@deltakosh said to go for it :-) so I guess @RaananW will merge tomorrow after the perf tests he wanted to finalize ?

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

Successfully merging a pull request may close this issue.

3 participants