Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b9b2236
generalize lerp() to N dimensions
mawerb Jul 20, 2026
997089d
Updated function documentation to match new behaviour
mawerb Jul 20, 2026
64bf39f
Added unit test to handle case mentioned in issue ticket
mawerb Jul 20, 2026
e708de4
Added 4d example of lerp to function documentation
mawerb Jul 20, 2026
30bd6ad
Modified _validatedVectorOperation to take in trailing arguments for …
mawerb Jul 20, 2026
1eb9358
Update tests to properly test edge cases mentioned in issue ticket f…
mawerb Jul 20, 2026
14246f2
updated documentation to match new structure of lerp()
mawerb Jul 20, 2026
a53af53
Merge branch 'main' into vector-lerp-generalize-dimensions
perminder-17 Jul 20, 2026
9c20326
refactored patch vector error checks into a helper function
mawerb Jul 21, 2026
9a22554
Merge branch 'main' into vector-lerp-generalize-dimensions
mawerb Jul 21, 2026
ecdfec9
Merge branch 'main' into vector-lerp-generalize-dimensions
perminder-17 Jul 22, 2026
ec21120
refactored lerp() examples to output results with print() to text()
mawerb Jul 22, 2026
603e581
Merge branch 'main' into vector-lerp-generalize-dimensions
davepagurek Aug 12, 2026
0aa9503
Merge branch 'main' into vector-lerp-generalize-dimensions
perminder-17 Aug 12, 2026
2249060
Remove obsolete lerp spy test restored by main merge
mawerb Aug 13, 2026
99c9622
Merge branch 'main' into vector-lerp-generalize-dimensions
mawerb Sep 8, 2026
4cbe1b7
fixed typescript error from putting rest parameter at start of vector…
mawerb Sep 9, 2026
76db118
Merge branch 'main' into vector-lerp-generalize-dimensions
mawerb Sep 9, 2026
5bd7cb1
Merge branch 'main' into vector-lerp-generalize-dimensions
ksen0 Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 114 additions & 22 deletions src/math/p5.Vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2313,63 +2313,150 @@ class Vector {
}

/**
* Calculates new `x`, `y`, and `z` components that are proportionally the
* Calculates new vector components that are proportionally the
* same distance between two vectors.
*
* `lerp()` can use separate numbers, as in `v.lerp(3, 3, 0.5)`, another
* <a href="#/p5.Vector">p5.Vector</a> object, as in `v.lerp(v2, 0.5)`, or an
* array of numbers, as in `v.lerp([3, 3], 0.5)`. When using numbers, the last
* argument is always the interpolation amount.
*
* The `amt` parameter is the amount to interpolate between the old vector and
* the new vector. 0.0 keeps all components equal to the old vector's, 0.5 is
* halfway between, and 1.0 sets all components equal to the new vector's.
* Values of `amt` outside the range 0 to 1 are allowed and extrapolate beyond
* the target.
*
* This method supports N-dimensional vectors. You should interpolate vectors
* only when they are the same size. When two vectors of different sizes are
* used, the smaller dimension will be used, and any additional values of the
* longer vector will be ignored.
*
* The static version of `lerp()`, as in `p5.Vector.lerp(v0, v1, 0.5)`,
* returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
* the original.
* the originals.
*
* @param {Number} x x component.
* @param {Number} y y component.
* @param {Number} z z component.
* @param {Number} amt amount of interpolation between 0.0 (old vector)
* and 1.0 (new vector). 0.5 is halfway between.
* @param {...Number} args target vector components followed by the
* interpolation amount. The last argument is always
* `amt` (0.0 keeps the old vector, 1.0 the new
* vector, 0.5 is halfway between).
* @chainable
*
* @example
* // META:norender
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Vector object.
* let v0 = createVector(1, 1, 1);
* let v1 = createVector(3, 3, 3);
*
* // Interpolate.
* v0.lerp(v1, 0.5);
*
* // Prints "p5.Vector Object : [2, 2, 2]" to the console.
* print(v0.toString());
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v0.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [2, 2, 2]" written on a gray square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Vector object.
* let v = createVector(1, 1);
*
* // Interpolate with numbers. The last argument is the amount.
* v.lerp(3, 3, 0.5);
*
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [2, 2]" written on a gray square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Vector object.
* let v = createVector(1, 1, 1);
*
* // Interpolate with an array.
* v.lerp([3, 3, 3], 0.5);
*
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [2, 2, 2]" written on a gray square.');
* }
*
* @example
* // META:norender
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Vector object.
* let v = createVector(1, 1, 1);
*
* // Interpolate.
* v.lerp(3, 3, 3, 0.5);
*
* // Prints "p5.Vector Object : [2, 2, 2]" to the console.
* print(v.toString());
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [2, 2, 2]" written on a gray square.');
* }
*
* @example
* // META:norender
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create p5.Vector objects.
* let v0 = createVector(1, 1, 1);
* let v1 = createVector(3, 3, 3);
*
* // Interpolate.
* let v2 = p5.Vector.lerp(v0, v1, 0.5);
*
* // Prints "p5.Vector Object : [2, 2, 2]" to the console.
* print(v2.toString());
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v2.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [2, 2, 2]" written on a gray square.');
* }
*
* @example
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create p5.Vector objects.
* let v0 = createVector(0, 1, 0, 1);
* let v1 = createVector(1, 0, 1, 0);
*
* // Interpolate.
* v0.lerp(v1, 0.5);
*
* // Display the result.
* textAlign(CENTER, CENTER);
* text(v0.toString(), 0, 0, width, height);
*
* describe('The text "p5.Vector Object : [0.5, 0.5, 0.5, 0.5]" written on a gray square.');
* }
*
* @example
Expand Down Expand Up @@ -2414,18 +2501,23 @@ class Vector {
* pop();
* }
*/
/**
* @param {Number[]} arr array to lerp towards.
* @param {Number} amt
* @chainable
*/
/**
* @param {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to lerp toward.
* @param {Number} amt
* @chainable
*/
lerp(x, y, z, amt) {
if (x instanceof Vector) {
return this.lerp(x.x, x.y, x.z, y);
lerp(values, amt) {
const minDimension = prioritizeSmallerDimension(this.dimensions, values);
shrinkToDimension(this.values, minDimension);

for (let i = 0; i < this.values.length; i++) {
this.values[i] += (values[i] - this.values[i]) * amt;
}
this.x += (x - this.x) * amt || 0;
this.y += (y - this.y) * amt || 0;
this.z += (z - this.z) * amt || 0;
return this;
}

Expand Down
90 changes: 71 additions & 19 deletions src/math/patch-vector.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Vector } from './p5.Vector.js';

function _vectorError(instance, message = 'Requires valid arguments') {
if (!instance.friendlyErrorsDisabled()) {
instance._friendlyError(message, 'p5.Vector');
}

return instance;
}

/**
* @private
* @internal
Expand All @@ -24,17 +32,69 @@ export function _defaultEmptyVector(target) {
/**
* @private
* @internal
* @param {Boolean} expectsSoloNumberArgument whether a single number scales
* every component (mult/div/rem)
* @param {Number} [trailingArgCount=0] number of trailing args to peel off
* before treating the rest as vector components (e.g. 1 for lerp's amt)
* @param {Number[]} [trailingDefaults] if provided, used when only component
* values are passed (no trailing args), e.g. `[0]` so `v.lerp(other)` uses amt 0
*/
export function _validatedVectorOperation(expectsSoloNumberArgument) {
return function (target) {
export function _validatedVectorOperation(
expectsSoloNumberArgument,
trailingArgCount = 0,
trailingDefaults
) {
return function(target) {
return function (...args) {
if (args.length === 0) {
const trailing = [];

if (trailingArgCount > 0) {
const onlyValues =
args.length === 1 &&
(args[0] instanceof Vector || Array.isArray(args[0]));

if (onlyValues && trailingDefaults) {
for (let i = 0; i < trailingDefaults.length; i++) {
trailing.push(trailingDefaults[i]);
}
} else if (args.length < trailingArgCount) {
if (trailingDefaults && args.length === 0) {
// No arguments? No action (same as other vector ops)
return this;
}
return _vectorError(this);
} else {
for (let i = 0; i < trailingArgCount; i++) {
trailing.unshift(args.pop());
}

for (let i = 0; i < trailing.length; i++) {
const t = trailing[i];
if (typeof t !== 'number' || !Number.isFinite(t)) {
return _vectorError(this, 'Arguments contain non-finite numbers');
}
}

if (args.length === 0) {
return _vectorError(this);
}
}
} else if (args.length === 0) {
// No arguments? No action
return this;
} else if (args[0] instanceof Vector) {
}

if (args[0] instanceof Vector) {
// Do not allow extra args after a vector when trailing args are used
if (trailingArgCount > 0 && args.length > 1) {
return _vectorError(this);
}
// First argument is a vector? Make it an array
args = args[0].values;
} else if (Array.isArray(args[0])) {
if (trailingArgCount > 0 && args.length > 1) {
return _vectorError(this);
}
// First argument is an array? Great, keep it!
args = args[0];
} else if (args.length === 1) {
Expand All @@ -48,28 +108,16 @@ export function _validatedVectorOperation(expectsSoloNumberArgument) {
for (let i = 0; i < args.length; i++) {
const v = args[i];
if (typeof v !== 'number' || !Number.isFinite(v)) {
if (!this.friendlyErrorsDisabled()) {
this._friendlyError(
'Arguments contain non-finite numbers',
'p5.Vector'
);
}
return this;
return _vectorError(this, 'Arguments contain non-finite numbers');
}
}
} else {
if (typeof args !== 'number' || !Number.isFinite(args)) {
if (!this.friendlyErrorsDisabled()) {
this._friendlyError(
'Arguments contain non-finite numbers',
'p5.Vector'
);
}
return this;
return _vectorError(this, 'Arguments contain non-finite numbers');
}
}

return target.call(this, args);
return target.call(this, args, ...trailing);
};
};
}
Expand Down Expand Up @@ -103,4 +151,8 @@ export default function vectorValidation(p5, fn, lifecycles) {
'p5.Vector.prototype.sub',
_validatedVectorOperation(false)
);
p5.registerDecorator(
'p5.Vector.prototype.lerp',
_validatedVectorOperation(false, 1, [0])
);
}
Loading
Loading