Convenient THREE.Vector3 caculation? And without modify itself?

I feel THREE.Vector3 calculation is very very hard to use, because most of the calculations will change itself. I have to keep new copy clone, or define global Vector3s at the beginning of the code.
This setting of changing its own value should be for performance considerations? But is there a solution that is both easy to use and has high performance?
It is best to use the one that is close to the glsl experience.

1 Like

Can you give an example in which you’d have to use .clone() more than once :thinking: ? Only the initial vector in the calculation chain is mutated - methods, afair, don’t modify argument vectors.

Oh, I mean, if I have multiple target Vector3s to calculate, or need some intermediate Vector3s, I’ll need multiple new copy clone or global Vector3s.
If just one target Vector3, it should just need one new copy clone and do all the calulations in one chain.

Nevertheless, even if only need to clone once, this setting of changing its own value is very unfavorable for logical thinking. I hope there is a way to make the calculation manner of Vector3 in js more like in glsl.

I agree mutability is usually unwanted - on the other hand, changing it would require the entire API to be modified (matrices, quaternions, objects - pretty much everything in three works thanks to mutations.)

Also 100k issues would appear overnight on GH if it would be changed. :’)

Yes, we definitely cannot change the existing interface, so many classes using this manner as you metioned.
For Vector3, I tried to define function like function vec3(x,y,z){ return new THREE.Vector3(x,y,z) }, but it’s obviously a very poor performance practice. And because we cannot overload operators such as + - * /, it’s still very hard to use.
I have also tried to find other js math libs, but if use them, it means that will lose a lot of functions in three.js.

Has anyone tried a js library for example like this? How about the performance? Is it possible for three.js to make a new set of interfaces in this way?

For + - * /, there are addXXX, subXXX, multiplyXXX, divideXXX methods.

For + - * / , there are addXXX, subXXX, multiplyXXX, divideXXX methods.

I think that for simple calculations, they are almost the same, but for complex calculations, methods are too much trouble than operator.

How would you prefer the API work? You cannot do operator overloading in JS so you cannot make it work similar to GLSL. I don’t think anybody prefers this style of vector calculations but it’s the best we get given the garbage collector and other limitations of Javascript. Bablyonjs and glmatrix both use a similar model for their math calculations.

6 Likes

Most math-related functions within three.js itself are implemented by allocating 1-2 reserved vectors, and reusing them each time the function is called. For a simplified example:

_v1 = new Vector3();

function doMathThing(position /* Vector3 */, target /* Vector3 */) {
  // Write input to reserved vector.
  _v1.copy(position);

  // Do calculations.
  _v1.add(...);
  _v1.multiply(...);
  // ...

  // Write to output vector.
  return target.copy(_v1);
}

This pattern has flat memory usage, which is a major benefit over immutable patterns — particularly in a render loop running at 60+ FPS, you don’t want to create work for the Garbage Collector. I’d recommend using this same pattern in your code, for anything in the render loop.

8 Likes

Out of curiosity @donmccurdy - could you explain how it does limit the memory usage by doing vector pre-allocation :thinking: ? It does sound smart, and is definitely readable in the source code, but just by taking a look at an example I can’t see how it optimises the memory usage (except maybe for caching previous results / refs to vectors):

const myVector = new Three.Vector3(1.0, 0.0, 0.0);
// (1) We create a new vector

const recalculatedVector = myVector.clone().add(new Three.Vector3(0.0, 1.0, 0.0));
// (2) We clone the original vector
// (3) We use Vector3.add which on itself, as you mentioned, uses 2 more built-in Vector3's instead of just using myVector and the passed argument Vector3

Wouldn’t mutating the callee directly be more optimal for the memory than using pre-allocated vectors? The callee vector has to exist anyway to call any method of it - unless I miss something obvious, pls pardon ignorance. :’)

1 Like

I’ll compare three options, since I’m not sure which two of the three you’re asking about.

self.add(other); // (a) updates self
self.add(other); // (b) updates other
self.add(other); // (c) returns a new vector

If you’re just calling self.add(other) a single time, (a), (b), and (c) are basically the same. The memory cost of a single Vector3 is negligible. But if you’re calling the method repeatedly, e.g. in a render loop, then…

(a) and (b) do not allocate memory, so they’re still the same from a performance perspective. I assume three.js uses (a) because it’s chainable and more standard for JavaScript, but ultimately that’s a subjective choice. (c) is the “bad” one, since it allocates a new vector each time it is called.

As the developer, you could still put yourself in a performance rut with (a) or (b) by cloning things before you call the method. If you’re not hitting any performance limits, or not doing this in a render loop, that could be fine. But there’s really never any reason you would need to call new Vector3() or vector.clone() inside your render loop, and to minimize GC you should avoid that.

// GOOD
var _v1 = new Vector3();
var _delta = new Vector3(0, -1, 0);

function animate () {
  requestAnimationFrame(animate);

  var myVector = mesh.position;
  var recalculatedVector = _v1.copy(myVector).add(_delta);
  // ...
}

///////////////////////////////////

// BAD
function animate () {
  requestAnimationFrame(animate);

  var myVector = mesh.position;
  var recalculatedVector = myVector.clone().add(new Vector3(0, -1, 0));
  // ...
}

three.js doesn’t force you to manage memory this carefully, but it does need to ensure that if you take the time to manage your memory, the three.js API will accommodate that and not create work for GC itself.

6 Likes

Yeah, that’s what my question was about - thanks! :smile:

I found a hacking operator overloading method maybe a solution, still trying.

This library gives javascript coders a way to handle operators with a single statement () => aVec * bVec - dVec . The calculation can be combined with numbers () => aVec * bVec * 4 - dVec - 1.5 .

ref: Javascript: operator overloading - Stack Overflow

I not dig into the source code of GitHub - basics/vector: This library provides 3D Vector in js including arithmetic operator overloading (+ - * / % **)., but with the hint of it:

This Vector class calls the assigned statement three times for x , y and z .

and other informations:

I made a quick implementation:

class Vec3 {
	static state = 'x'
	constructor(x = 0, y = 0, z = 0) {
		this.x = x; this.y = y; this.z = z;
	}
	valueOf() {
		return this[Vec3.state]
	}
}
function vec3(x, y, z) {
	return new Vec3(x, y, z)
}
function calc(fn) {
	let result = vec3()
	Vec3.state = 'x'; result.x = fn();
	Vec3.state = 'y'; result.y = fn();
	Vec3.state = 'z'; result.z = fn();
	return result
}
let a = vec3(1, 2, 3);
let b = vec3(0, 1, 0);
let c = calc(() => a * 10 + b + vec3(0, 0, 2))

console.log(c); // Vec3 {x: 10, y: 21, z: 32}

It works well! At least for Vec3 type only and simple operators.

======EDIT======

extends THREE.Vector3 , cross and dot OK.

Demo (see devtool log)

usage:
let z = calc(() => (a * 10 - b + 3) / (cross(a, b) + dot(a, b) * 3))
, it’s JavaScript!!!

Compared with normal method:
(a.clone().multiplyScalar(10).sub(b).addScalar(3)).divide(new THREE.Vector3().crossVectors(a, b).addScalar(a.dot(b) * 3))

2 Likes

I was annoyed yesterday that there is no support for calculations on “direct values” (e.g. sth like v.add(x,y,z), and got here on my search for alternatives. Is there a reason nothing like it exists? Your solution is good, but it still uses extra assignments every time.

A little background on my problem: I have a huge landscape with local “landscape gradients” which are used for an animation, talking about several M gradients. I load the gradients as a Float32Array which is very fast, and also MUCH more efficient memory-wise than the millions of Vector3 objects that I have to create out of it in order to use it with other three.js objects. As far as I know there is currently no way to make use of the Float32Array directly (like the method above, or something like a Vec3-class that is backed by the Float32Array)
Would it be something worth considering for the future?

This topic was discussed a GitHub in the past. However, no change was done to avoid additional complexity and keep the math classes simple.

2 Likes

If it’s helpful to anyone, I’ve finally reached a happy place with my js vector math by using three.js with haxe, a language that compiles to js that supports operator overloading

Through that you can have a full implementation of GLSL-style vector math operations, including swizzling and so on. That code then gets compiled into plain operations on local variables rather than objects, so there’s much less new-ing and object allocation

For example, the following vector math

length(mat2(2) * vec2(3, 4) * 0.5 - vec2(0.5));
// generates
Math.sqrt(18.5)

More information on the library GitHub - haxiomic/vector-math: Shader-math in haxe: library for GLSL vector operations, complete with swizzles and all

I’ve created a starter project for anyone curious to try this:

1 Like