p5.js
p5.js copied to clipboard
vectors created using random2d() do not respect angleMode(DEGREES) as those created with createVector() do
Most appropriate sub-area of p5.js?
- [X] Math (Vectors)
Details about the bug:
https://p5js.org/reference/#/p5.Vector/random2D Random2d returns a p5.Vector just as createVector does.
https://p5js.org/reference/#/p5.Vector/heading states: "Calculate the angle of rotation for this vector(only 2D vectors). p5.Vectors created using createVector() will take the current angleMode into consideration, and give the angle in radians or degree accordingly."
This fiddle shows that this does not consider angleMode(DEGREEES) if the vector is created with random2d: https://replit.com/@jgordon510/p5-template-68#script.js
It would seem like there should be parity between random2d and createVector. A likely practice might be to hardcode certain values into a game for testing and then replace them with random vectors one the logic is working. In that case, the lack of parity would cause it to break unexpectedly.
random2D() uses fromAngle() behind the scenes so this issue exist in fromAngle() as well (rather the problem came from it).
There's a bit of extra functionality in createVector which is when it is called as an instance method it will bind the current instance this to the vector's p5 property. However because fromAngle() is a static function, it will not have access to the current instance and so cannot bind this to p5 in the same way.
Then looking into heading() it will check for the p5 property before attempting to convert the angles from radians to degrees as necessary.
The code's a bit spaghetti here, I'll need some time to fully unpack and come up with some suggestions but if anyone else wants to hop in with something, please do.
Thank you all for working on this issue. I am inviting the current Math stewards to this discussion @limzykenneth, @ericnlchen, @ChihYungChang, @bsubbaraman, @albertomancia, @JazerUCSB, @tedkmburu, @perminder-17, @Obi-Engine10, @jeanetteandrews. Would love to hear what y'all think. Thanks!
Hello @limzykenneth and @jgordon510, I'd like to work on this issue. I looked at the code a bit and I have a perplexity I cannot solve myself, so I ask you: What is the purpose of the variable this.isPInst? Is defined in the constructor as follows:
p5.Vector = class {
// This is how it comes in with createVector()
// This check if the first argument is a function
constructor(...args) {
let x, y, z;
if (typeof args[0] === 'function') {
this.isPInst = true; // <--------------------------------
this._fromRadians = args[0];
this._toRadians = args[1];
x = args[2] || 0;
y = args[3] || 0;
z = args[4] || 0;
// This is what we'll get with new p5.Vector()
} else {
x = args[0] || 0;
y = args[1] || 0;
z = args[2] || 0;
}
this.x = x;
this.y = y;
this.z = z;
}
and I found out that if this variable is false, then the function this._fromRadians is not defined. I think known this I will be able to come up with a solution for this issue. Thanks!
If you create a new p5.Vector(...) and pass in _fromRadians and _toRadians implementations into the constructor as the first two arguments, then isPInst records that the vector contains a reference to the p5 instance. The idea is that when you call createVector(...) from a running p5 instance, under the hood, it will pass those functions in, and their implementations will be able to reference the current degrees mode state: https://github.com/processing/p5.js/blob/37d3324b457b177319ed65468201f2806a66eff5/src/math/math.js#L77-L81
The difficulty is that if you're using p5 in instance mode, there may be more than one instance actively running, and they may have different degree modes:
const p1 = new p5(sketch => {
sketch.setup = () => {
sketch.angleMode(sketch.DEGREES)
}
})
const p2 = new p5(sketch => {
sketch.setup = () => {
sketch.angleMode(sketch.RADIANS)
}
})
setTimeout(() => {
const v = new p5.Vector(1, 0)
// Ambiguous which instance this will be for, so
// it can't easily tell what angle mode this should be in
v.rotate(Math.random())
}, 10_000)
In global mode, it's less ambiguous, so it may be feasible to make this work just for global mode and still leave the vectors disconnected from the instance's angle mode in instance mode.
Could changing the fromAngles function from a static method to an instance method be a good solution? in my understanding if fromAngles were not a static method it would have access to the current instance and we could define it like:
fromAngle(angle, length) {
if (typeof length === 'undefined') {
length = 1;
}
return new p5.Vector(this._fromRadians, this._toRadians,
length * Math.cos(angle), length * Math.sin(angle), 0);
}
I hope to not have said a stupid thing, because I am not really sure what are the benefit of having it as a static method, if that's so I am sorry and I would like to know why
You're right that it would have access to those properties in that case! The issue is that you would then have to call it on an instance of a vector, e.g. (in instance mode):
const v1 = sketch.createVector(1, 0);
const v2 = v1.fromAngle(45, 100)'
This feels a little awkward because you would need to have an existing vector in order to make a new one from an angle, and the new vector doesn't really have anything to do with the original one.
It might make sense to add an instance method to p5 itself, similar to the sketch.createVector method, like sketch.createVectorFromAngle(angle, length). Then, since this would be an instance of p5, we could access its angle mode state. That would mean that p5.Vector.fromAngle(angle, length) would still not be able to use the angle mode, but then we'd have an alternative to point people to that would be able to read it.