p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

[2.0] Complete extension of `p5.Vector` to n dimensions

Open GregStanton opened this issue 2 months ago • 15 comments

[2.0] Complete extension of p5.Vector to n dimensions

Some p5.Vector features have not yet been extended to $n$ dimensions, such as rem(). The purpose of this issue is to extend all such features. We can create sub-sub-issues for action items if multiple features need to be extended and if there are different volunteers for different features.

Tasks:

  • [ ] Extend rem() to $n$ dimensions.
  • [ ] Check all other operations to make sure they've been extended, when appropriate.

The first task can be assigned now and is ready for work. The second task can be assigned to the community: if you are reading this and you're able to find another feature that hasn't yet been extended, comment on this issue, and I'll add it to the list (please indicate if you'd like to work on it, or if you'd like another volunteer to do the implementation).

Update: We've mostly or entirely worked out which features need to be extended. I'm working on creating sub-issues for each one. In the meantime, attention may be directed to the following blocking issues:

  • #8153: Consensus would clarify the meaning of 2D vs. 3D, in the context of 2.x. (In 1.x, 2D meant $z=0$, but in 2.x, 2D will mean the vector has two elements, pending resolution of this issue.) This is important for extending features that need to distinguish these cases.
  • #8155: Consensus on a user-facing API for checking dimension size would enable a more stable implementation of features that operate on specific dimensions, such as cross(), heading()/setHeading(), and rotate().

GregStanton avatar Oct 15 '25 15:10 GregStanton

Hello @GregStanton id like to help with Task 2

I see the beta documentation mentions that add() supports n-dimensional vectors

Should I use that as a reference for how the extended methods should work or would you prefer I look at the rem() implementation as the pattern to follow?

Once I identify the methods, I'll be happy to implement them

atif09 avatar Oct 15 '25 17:10 atif09

@atif09. Thank you for offering to help. @GregStanton invited anyone to check for operations that need to be extended and I've attempted that. I'll post my findings in the next comment. Please review them and the p5.Vector code to see what I've missed or got wrong and add your own comments. Many of the changes need to wait for other issues, but a few can be implemented now. Feel free to volunteer to do so.

Also check out the discussion on other sub-issues for issue #8149 and add your own thoughts.

sidwellr avatar Oct 18 '25 22:10 sidwellr

I've reviewed all the vector operations and note the following that need some changes to support n dimensions. I'll defer actual implementation to another volunteer.

add() supports n dimensions, but needs to be updated per issue #8159.

rem() does not support n dimensions (as noted above); I suggest updating it after div() is done to use as a pattern.

sub() supports n dimensions, but has a NaN bug (issue #8117) and needs to updated per issue #8159 (which will likely also fix the NaN bug).

mult() supports n dimensions, but needs to be updated per issue #8159.

div() supports n dimensions, but has a NaN bug when the dividend has a longer length than the divisor, and needs to updated per issue #8159 (which will likely also fix the NaN bug).

cross() can't support n dimensions since the cross product is only defined for 3D vectors. It needs to throw an error when either vector has more than three dimensions. If either vector has less than three dimensions, it should pad it with zeros to three dimensions; that currently works because it uses x, y, and z, but may need to be modified depending on the disposition of issue #8153.

heading(), setHeading(), and rotate() only work with 2D vectors; they should throw an error if any other dimension is used. But for compatibility with 1.x, they should accept a 3D vector with a z of 0 and just ignore the z value. (These can be worked now.)

angleBetween() uses cross() so currently only works for 3D vectors (or 2D vectors with assumed z=0). It needs to be rewritten to support n dimensions using dot() instead of cross(). Issue #8159 might apply if the vector dimensions don't match.

lerp() and slerp() are hard coded to use 3D vectors and need to be rewritten to support n dimensions. Issue #8159 might apply if the vector dimensions don't match.

equals() needs to check if the dimensions of the two vectors are equal and return false if not. (This can be worked now.)

sidwellr avatar Oct 18 '25 22:10 sidwellr

@atif09: Thank you for offering to help! Are you interested in working on rem()? That may be a good place to start, since it was the first issue we observed, and it might not be as complicated as some of the other issues.

@sidwellr: Thank you so much for compiling and sharing that list! That's very helpful. For now, I'll just comment on a few things.

Before work begins, I may create sub-issues of the current issue for each feature, as described in the top post. This would encourage PRs with narrower scopes, which may be easier to review, while also allowing other volunteers to chip in if needed. Another potential advantage is that it may allow clearer discussion of the more subtle tasks (whether in issues or PRs), such as these:

  1. I previously described how we can extend angleBetween() using dot() as @sidwellr mentioned. This may require some care around numerical issues.
  2. The rotate() method should likely be made consistent with the standalone rotate() function, which adds an optional axis parameter in order to support rotations in 3D. This would also be similar to e.g. .applyAxisAngle() in three.js. Higher-dimensional rotations are possible in principle but might be considered out of scope for p5, as it takes extra work for users to specify them (e.g. via rotation matrices).
  3. The reflect() method can reflect $n$-dimensional vectors through a hyperplane via a simple formula, without requiring extra effort of users. It works the same way as in 2D and 3D, by specifying a normal vector as input. So we may want to update this method.
  4. There are some special considerations regarding the equals() method. It might not be necessary to block work on this, but basically, it's standard and useful to provide an elementwise comparison that supports broadcasting, e.g. for creating Boolean masks. This isn't how the current method works. We probably do want to preserve the current behavior, but it may be worthwhile to discuss an API for the other behavior.

Regarding dimension mismatches, I'm imagining that we would handle those in separate tasks, potentially as sub-issues of #8159. The current issue could then be devoted to correctly handling input with matching dimensions of any size, allowing laser-focused tasks that aren't blocked by #8159.

GregStanton avatar Oct 19 '25 13:10 GregStanton

sounds good @GregStanton! id be happy to work on extending the rem() function to support n-dimensional vectors

looking at @sidwellr's analysis i see the recommendation to use the div() method as a pattern after it's been updated, from reviewing the file, i understand ill need to modify the implementation to work with arrays of any length rather than just using x, y, z properties

should i go ahead and start on this now, or would you prefer i wait until you create the sub issues you mentioned?

atif09 avatar Oct 19 '25 19:10 atif09

I think adding an axis parameter to rotate() would be useful. Is it within the scope of this issue? It doesn't extend anything to n dimensions.

I thought reflect() would work correctly on n-dimensional vectors since it just uses other Vector methods to compute the reflection. I think it works fine for any dimension, but please verify this if you have concerns. Here is a quick example:

function setup() {
  // Create a normal vector.
  let n = createVector(1, 1, 1, 1).normalize();
  // Create a vector to reflect.
  let v = createVector(4, 6, 8, 10);

  // Reflect v about n.
  v.reflect(n);

  // Prints "[-10, -8, -6, -4]" to the console.
  print(v.toString());
}

I suppose it could be useful to have a comparison method that supports broadcasting, but I think it would be confusing if equals() said that [2] and [2,2,2,2,2] were the same. It also breaks transitivity: if [2,2] = [2] and [2] = [2,2,2] that should imply that [2,2] = [2,2,2].

sidwellr avatar Oct 20 '25 03:10 sidwellr

@atif09: Thanks again for offering to help! I'll put together a new sub-issue and assign it to you shortly, and I'll put together sub-issues for the other features as soon as I get a chance.

@sidwellr:

Thanks for all your thoughts as always!

I think adding an axis parameter to rotate() would be useful. Is it within the scope of this issue? It doesn't extend anything to n dimensions.

Glad you like the idea! Yep, this should be in scope, as it extends rotations as far as we can reasonably extend them, given the current API. In particular, it extends from 2D to 3D.

I thought reflect() would work correctly on n-dimensional vectors since it just uses other Vector methods to compute the reflection. I think it works fine for any dimension, but please verify this if you have concerns.

Yep! The current implementation should already work in $n$-dimensions. Now we just need to document this behavior, which falls under the scope of the current issue. (The intent of the umbrella issue #8149 is to include documentation changes along with sub-issues. The p5 PR template includes a checkbox for documentation updates, so usually this would happen as a matter of course, but in this case it's only the docs that require an update.)

I suppose it could be useful to have a comparison method that supports broadcasting, but I think it would be confusing if equals() said that [2] and [2,2,2,2,2] were the same.

Oh, I should clarify. An elementwise comparison that supports broadcasting wouldn't return true when comparing [2] and [2, 2, 2, 2, 2]. It'd return [true, true, true, true, true]. Comparing [2] and [2, 1, 2, 2, 2] would return [true, false, true, true, true]. See, for example, the equal() function of TensorFlow.js. However, I just worked out an API for this that fits into longer-term proposals I've been working on, and it's purely an additive API. So I think we can go ahead with returning false whenever the dimension and elements don't exactly match.

GregStanton avatar Oct 22 '25 19:10 GregStanton

Okay @atif09, I set up the sub-issue for rem() and can assign it to you. Would you mind commenting on that issue? It seems I can't assign the issue to you until you do. Thanks!

GregStanton avatar Oct 22 '25 19:10 GregStanton

Sure! I'll comment on it

Okay @atif09, I set up the sub-issue for rem() and can assign it to you. Would you mind commenting on that issue? It seems I can't assign the issue to you until you do. Thanks!

atif09 avatar Oct 22 '25 19:10 atif09

heading(), setHeading(), and rotate() only work with 2D vectors; they should throw an error if any other dimension is used. But for compatibility with 1.x, they should accept a 3D vector with a z of 0 and just ignore the z value. (These can be worked now.)

Id love to work on any one of these, but from what i understood i should wait until @GregStanton creates the subissues for them, correct?

justAnotherAnotherUser avatar Oct 24 '25 17:10 justAnotherAnotherUser

Hi @justAnotherAnotherUser! Yes, I'll get to work on creating the sub-issues this weekend, so that we can discuss, assign, and track the work more easily. If you comment on a sub-issue that interests you, I can assign it to you then. Also, just FYI, a bit of community discussion may be required before work begins in some cases. I'll include write-ups in the sub-issues to explain what's left to discuss.

GregStanton avatar Oct 24 '25 22:10 GregStanton

Hi @justAnotherAnotherUser! I'm currently adding sub-issues for the features you expressed interest in (heading(), setHeading(), and rotate()). I've already put up a sub-issue for heading() (#8214). If you add a comment on that issue, I'll be able to assign it to you. I'll put up the other two issues shortly.

GregStanton avatar Oct 28 '25 15:10 GregStanton

Hi @GregStanton i have replied to you on #8214

justAnotherAnotherUser avatar Oct 28 '25 16:10 justAnotherAnotherUser

Hi — I'm Shubham Kahar and I support this proposal.
It improves consistency and clarity for vector users, and I think it will simplify both API usage and documentation.
Happy to help test or document once the final decision is made. 🙂

Shubhamkahar196 avatar Nov 12 '25 16:11 Shubhamkahar196

Hi everyone, thanks so much for all the lively discussion of the p5.js 2.x Vector implementation! Now that that 2.1 is released, we wanted to set up a more direct discussion space for p5.js 2.x Vector implementation bugfixes, documentation, and improvements. So, here is a Discord channel: https://discord.gg/gH3VcRKhen

As we discuss/unblock each of the vector issues, I will also follow up on those issues as a comment. So if you prefer to participate only (or primarily) on GitHub, that still also works!

ksen0 avatar Nov 14 '25 09:11 ksen0