processing / p5.js

p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. http://twitter.com/p5xjs —
http://p5js.org/
GNU Lesser General Public License v2.1
21.47k stars 3.29k forks source link

Allow the rotate function to specify an axis in 2D #4668

Open olleicua opened 4 years ago

olleicua commented 4 years ago

Most appropriate sub-area of p5.js?

Feature enhancement details:

I want to be able to rotate objects in 2D without the center of rotation being the origin. I noticed that it is possible to move the origin using the translate function but this then requires me to reposition objects with respect to the new origin and that won't work if I want different objects to rotate around different centers (for example to make a 2D simulation of a planetary system with moons).

I'm hoping to support the following syntaxes:

rotate(angle, centerX, centerY);
rotate(angle, createVector(x, y));

Implementation

I'm planning to try to implement this myself but any thoughts others have on implementation details are welcome :)

welcome[bot] commented 4 years ago

Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

joemckay5 commented 4 years ago

You can translate from your translate if you push without popping. Here's a sun-earth-moon example https://editor.p5js.org/Joemckay/sketches/GHYH1-ugr

limzykenneth commented 4 years ago

Hi, if you need help with your own code, please direct your questions to the forum. We use GitHub issues mainly for bug reports and feature requests only. Thanks!

olleicua commented 4 years ago

Thanks joemckay5. Using the translate function in this way works but I still find it a bit counter-intuitive because it requires me visualize a rotating frame of reference. I can do this if I have to but I'm not sure it is the easiest way for everyone. Given that part of p5's goal is to be accessible to as many coders as possible I think it would be best to support both the method in your example and the syntax I proposed above. We should be able to accomplish this by internally calling translate before and after the rotation if an axis is passed in.

@limzykenneth I'm working on a PR for this right now. I'm not able to re-open the issue. Is there a formal decision making process for deciding whether a contribution will be accepted? Should I abandon my PR? Please advise.

limzykenneth commented 4 years ago

In general we don't try to make the library do everything in every way possible so I would not recommend adding the complexity of a special rotate signature that ignores translate (would it ignore rotates as well?).

I would suggest holding off on a PR for now until there is a concensus about whether to add this feature or not.

olleicua commented 4 years ago

It wouldn't ignore translate. It would do the translate for you. It would work like the transform-origin css property. Basically the user passes in the coordinates within whatever the current frame of reference is (including any translates that have previously happened) and then internally the library could call translate(x, y) then rotate then translate back with translate(-x, -y). The reference already describes this interface for the 3D case so it seems more consistent to make it work in the 2D case as well. The implementations is pretty simple and I think it would make it a lot easier to use while still being backwards compatible.

limzykenneth commented 4 years ago

I find in this case the solution suggested by @joemckay5 easier to work around, the reference frame can be prevented from rotating by having a pair of push() pop() around them.

In the 3D case it is different. The rotation origin still stays the same and controlled by translate(). The second argument is the axis in which to rotate (normal to the plane of rotation), not the origin (since there is more degress of freedom in 3D, and there's only one valid axis to rotate around in 2D).

olleicua commented 4 years ago

You say here "there's only one valid axis to rotate around in 2D". This is completely false it is possible to rotate a 2D object around any point.

I recognize that there are ways of doing this without my feature. The point I'm trying to make is that for some people the existing ways feel counter-intuitive. Forcing the developer to visualize a transformation of the reference frame in order to rotate one object in an intuitive way (something they may not even consider trying) introduces unnecessary friction. Reading the p5 website and github page gives the impression of a community interested in welcoming anyone and everyone and respecting that different people may have different ways of approaching code. If you are serious about these goals of inclusivity then it seems like rejecting my proposal out of hand might be a mistake.

Let me put it this way: what is the down-side of accepting my PR. It adds minimal complexity. It doesn't break any existing code. I'm offering to write it and test it. Please reconsider.

joemckay5 commented 4 years ago

I'm not fully understanding how your proposed code would make the sun-earth-moon simulation easier.
If you just want the earth to rotate around the sun then rotateAround(locSun, locEarth, angle, radius) makes sense. But then how do you rotate the moon? P5 doesn't have a screenX screenY function like processing, so if you're using translate and rotate how does your rotateAround function return the location of the earth? I can't write rotateAround(locEarth, locMoon, angle, radius) if we don't know the location of the earth. We'd need to nest a rotateAround() inside another rotateAround(), but now we're duplicating the complexity you're seeking to avoid. The easiest answer is to avoid push, translate, rotate, pop, all together, and just use trig to get the actual locations of all the celestial bodies. But to my thinking, the simplicity of this sketch doesn't beg out for more simplification. https://editor.p5js.org/Joemckay/sketches/EgMjCFkeY

olleicua commented 4 years ago

https://editor.p5js.org/olleicua/sketches/CSpYx_IFS

The above is what I am proposing and it is definitely simpler than the trig solution for someone less familiar with trigonometry as wells as avoiding all of the complications of push/pop that a novice programmer might find more challenging. I really want to emphasize that it isn't really relevant whether you or I feel that a solution we have found begs for simplification. What is relevant is whether the p5 library provides tools that will allow any given novice programmer to build whatever they might want to build. If some people find using translate or trigonometry less intuitive and we are able to provide them with tools they might find more intuitive (without taking away any of the current options) then I see no reason we shouldn't do that. Not to put too fine a point on it but saying "this way works and it makes sense to me" feels dangerously close to code snobbery which I was led to believe we don't do here.

olleicua commented 4 years ago

As a side-note, an advantage of the code I linked in my most recent comment is that if you comment out either or both of the calls to proposedRotate you get exactly the result you would expect. This makes debugging easier (something a lot of novice programmers struggle with).

joemckay5 commented 4 years ago

Try drawing anything after you call your function and it will also be rotating. Push and pop are necessary because it resets the rotation as well as the position. At this point I agree with limzykenneth that this conversation belongs in a different forum. I think a lot of what you're perceiving as snobbishness is due to the context and in another setting people will be more interested and generous in helping you think through your idea. Good luck.

dhowe commented 4 years ago

Just going to jump here with my two-cents that a version of rotate (in 2d) that takes the point of rotation might actually be rather useful for new students. Each year I have one or more students who need to do a simple rotation in 2d to complete a piece, well before we have done affine transforms and I have to tell them either to wait, or to go ahead to that lesson on their own. For the 2d case at least, the only argument used for rotate in the renderer is the angle, so it would be relatively easy to allow 2nd and 3rd optional args for the point of rotation (not sure what this would require in the 3d renderer to keep the APIs in sync).

chen-ni commented 4 years ago

@olleicua push() and pop() are really powerful and essential to p5. In my understanding, your code is a workaround to avoid using push() and pop(). It does work in some cases and I would even agree that it's more intuitive than using push() and pop(), but at some point down the road, one would have to get used to using push() and pop(). For example, what if one has to simulate the rotation (turning on its own axis) as well as the revolution (moving around another body) in the solar system? What if one has to add another body that moves around the moon? Things like these can be easily done with the help of push() and pop() and could be really tricky otherwise.

So, what I'm suggesting is that instead of inventing some "alternative" to push() and pop(), just get used to them. You will love them.

montoyamoraga commented 4 years ago

hi @olleicua thank you for your contribution!

i struggle myself with rotation and translation, and i rely on the documentation we have, and i use a lot push() and pop() to not lose track of what i am doing and i do love the push and pop paradigm :)

maybe a good solution is you can help is to include your function as an example, or adding it side by side to an example using the existing combination of push, pop, rotate, translate so that beginners can grasp these concerts in a better and easier way, and we can improve our examples and documentation, without adding more complexity to the library as @limzykenneth suggested :)

i will be checking this discussion so we are all happy about our collective decision ~

dhowe commented 4 years ago

I think that people are underestimating the conceptual leap for new students moving from basic commands and specific x/y positions (point, line, rect, etc) to matrices/affine-transforms. Better coders should of course understand/use the latter, but there is a case to be made, in line with the p5js philosophy, that we should facilitate learners doing simple rotations in 2d with as little conceptual overhead as possible (we have a circle command after all).

So perhaps what's being proposed here, something like rotate2d(angle, [x], [y]) can be thought of as a convenience function, encapsulating the commands [translate(x,y), rotate(angle), translate(-x,-y)] into something potentially simpler for beginners to use. If so, then we should consider it. If, on the other hand, we think it will create additional confusion later on, or cause unexpected results in combination with others commands, then we shouldn't (@limzykenneth's point about how this would work with pre-existing rotations/translations is relevant)

joemckay5 commented 4 years ago

dhowe, I teach Processing too, and you're right, rotation is a tricky topic. I hope everyone is keeping in mind that the proposed function doesn't alleviate the need to push and pop.

lmccart commented 4 years ago

I guess I feel that "moving from basic commands and specific x/y positions (point, line, rect, etc) to matrices/affine-transforms" may not represent a complete beginner student anymore. So while this would be helpful for some, it may not necessarily be working toward our goal of expanding access, especially for those identifying with groups that have been historically marginalized.

dhowe commented 4 years ago

@lmccart can you clarify what you mean above? I'm not suggesting new people learn the internals of matrices - I only mean using the push/pop, translate, rotate, scale mode of drawing/thinking.

lmccart commented 4 years ago

I'm wondering, by the time you get to the point where a student is rotating an object around an axis, if we are beyond the "newbie" barrier to some extent.

dhowe commented 4 years ago

Probably depends on the case, though often the question comes up (in a first year uni class) when we do simple animations with variables (week 3-4?), well before drawing with transforms (week ~10).

mkmori commented 4 years ago

As a mid-level newbie with gaps in my math ed., (like matrix ops!), push()/pop() (pushMatrix()/popMatrix()) is pretty basic to enabling me to handle a whole range of multiple, complex, (incl. recursive!) transformations with any "elegance".

I would also take the opportunity to plug screenX/Y/Z (for "picking" and screen effects) and modelX/Y/Z (for construction)....

These features seem to keep coming up, often tangentially, as a bridge between "friendly" p5 transformation functions, (translate(), rotateX/Y/Z() etc.) and more complex operations, short of directly manipulating transformation matrices.

There seem to be technical limitations and performance issues tracking transformation matrices in a (2D) canvas: I've recently asked elsewhere if the same issues are obstacles in WebGL...?

Would it be feasible/desirable to implement P2D in WebGL, too?