Repository: playcanvas/engine (JavaScript)

Issue:
Add vector + scaled vector math
A feature request here.

I know, we don't want to bloat the vector math with convenience methods. However, this one I found I am using too often. Almost every time I do any vector math - when I am trying to add a scaled vector (without changing the original) to another vector.

Normally, one would do something like:
```js
// a + b*2;
const temp = new pc.Vec3();
temp.copy(b).mulScalar(2).add(a);
```

It requires you to allocate a temp vector, if you don't want to change the original. The only way to avoid allocation, you'd need to do a component wise writeout, which is verbose and would better be as part of the vector math methods:

```js
a.addScaled(b, scalar);

// where
a.x += b.x * scalar;
a.y += b.y * scalar;
a.z += b.z * scalar;
```


Discussion: Definitely could be useful.
You can also add a function to your Vec3 prototype, but agree, not as convenient.
Can this be called a more generic approach? Offseting an existing point with a scaled normal.

Example:

```js
pc.Vec3.prototype.mulAdd = function (a, s, b) {
    this.x = a.x + s * b.x;
    this.y = a.y + s * b.y;
    this.z = a.z + s * b.z;
}
const start = new pc.Vec3(100, 200, 300);
const normal = new pc.Vec3(0, 1, 0);
const end = new pc.Vec3();
end.mulAdd(start, 20, normal); // Vec3 {x: 100, y: 220, z: 300}
```

(the engine code itself is a good "test suite" for testing generic methods, so the extra methods can show how they shorten existing code in a potential PR)
@kungfooman I think your example is similar to my first snippet, where you are using a third vector to store the product. I personally prefer the chain variant, though, as it is self-explanatory of the operands in use just by following the chain. Not sure there is a benefit over current API:
```js
const temp = new pc.Vec3();
temp.copy(normal).mulScalar(20).add(start);
```

I think `addScaled` would be more generic in this case, as it will be up to developer to decide if a third vector storing the product to be allocated or not.
So now you prefer the chain variant and apparently have no problem using a temporary vector, why are you proposing a change?

Or what exactly does it make simpler? Like a real-life example of situations you encounter too often.
Sorry for not being clear :D What I meant is I do prefer the chain one over your suggested `.mulAdd(vec, scalar, vec)`, because both require a third vector, but a chain one is easier to read.

My proposal is to add a `.addScaled(vec, scalar)`. Which modifes the target vector in place, but doesn't create a temporary vector that holds a scaled one.

If we take your example, where I'd like to change the position of a point by some scaled normal, it would be:
```js
position.addScaled(normal, 20);
```
The benefit here, is I only use 2 vectors to end up with the same result as in your example, which uses 3.
No problem, I just want to hash it out a bit and that's why I asked for real life examples.

Because in most cases I feel that you usually have a starting point, so your example "in full" would still require chaining aswell:

```js
position.copy(start).addScaled(normal, 20);
```

But yes, I see your point, `addScaled` is a little bit "smaller" which by nature should apply to more situations.
You are correct there, it depends on case by case. It would be up to the developer to decide, whether a temp vector is needed. At least we give a choice. For example, this is a valid case as well:
```js
const pos = entity.getPosition();
pos.addScaled(normal, 20);
entity.setPosition(pos);
```



Fix (patch):
diff --git a/src/core/math/vec2.js b/src/core/math/vec2.js
index 10add95fa24..97765fbe2cf 100644
--- a/src/core/math/vec2.js
+++ b/src/core/math/vec2.js
@@ -103,6 +103,27 @@ class Vec2 {
         return this;
     }
 
+    /**
+     * Adds a 2-dimensional vector scaled by scalar value. Does not modify the vector being added.
+     *
+     * @param {Vec2} rhs - The vector to add to the specified vector.
+     * @param {number} scalar - The number to multiply the added vector with.
+     * @returns {Vec2} Self for chaining.
+     * @example
+     * const vec = new pc.Vec2(1, 2);
+     *
+     * vec.addScaled(pc.Vec2.UP, 2);
+     *
+     * // Outputs [1, 4]
+     * console.log("The result of the addition is: " + vec.toString());
+     */
+    addScaled(rhs, scalar) {
+        this.x += rhs.x * scalar;
+        this.y += rhs.y * scalar;
+
+        return this;
+    }
+
     /**
      * Returns an identical copy of the specified 2-dimensional vector.
      *
diff --git a/src/core/math/vec3.js b/src/core/math/vec3.js
index 7ebd8f9d309..e5b3b79b369 100644
--- a/src/core/math/vec3.js
+++ b/src/core/math/vec3.js
@@ -114,6 +114,28 @@ class Vec3 {
         return this;
     }
 
+    /**
+     * Adds a 3-dimensional vector scaled by scalar value. Does not modify the vector being added.
+     *
+     * @param {Vec3} rhs - The vector to add to the specified vector.
+     * @param {number} scalar - The number to multiply the added vector with.
+     * @returns {Vec3} Self for chaining.
+     * @example
+     * const vec = new pc.Vec3(1, 2, 3);
+     *
+     * vec.addScaled(pc.Vec3.UP, 2);
+     *
+     * // Outputs [1, 4, 3]
+     * console.log("The result of the addition is: " + vec.toString());
+     */
+    addScaled(rhs, scalar) {
+        this.x += rhs.x * scalar;
+        this.y += rhs.y * scalar;
+        this.z += rhs.z * scalar;
+
+        return this;
+    }
+
     /**
      * Returns an identical copy of the specified 3-dimensional vector.
      *
diff --git a/src/core/math/vec4.js b/src/core/math/vec4.js
index ea56ecca1c7..2e17f1931a6 100644
--- a/src/core/math/vec4.js
+++ b/src/core/math/vec4.js
@@ -127,6 +127,29 @@ class Vec4 {
         return this;
     }
 
+    /**
+     * Adds a 4-dimensional vector scaled by scalar value. Does not modify the vector being added.
+     *
+     * @param {Vec4} rhs - The vector to add to the specified vector.
+     * @param {number} scalar - The number to multiply the added vector with.
+     * @returns {Vec4} Self for chaining.
+     * @example
+     * const vec = new pc.Vec4(1, 2, 3, 4);
+     *
+     * vec.addScaled(pc.Vec4.ONE, 2);
+     *
+     * // Outputs [3, 4, 5, 6]
+     * console.log("The result of the addition is: " + vec.toString());
+     */
+    addScaled(rhs, scalar) {
+        this.x += rhs.x * scalar;
+        this.y += rhs.y * scalar;
+        this.z += rhs.z * scalar;
+        this.w += rhs.w * scalar;
+
+        return this;
+    }
+
     /**
      * Returns an identical copy of the specified 4-dimensional vector.
      *