Skip to content

Commit 2fdf7d5

Browse files
committed
PVector additions with tests for p5 API parity [#1981]
fromAngle, heading, lerp, magSq, random2D, random3D, rotate, setMag
1 parent fa3f3fd commit 2fdf7d5

File tree

2 files changed

+218
-7
lines changed

2 files changed

+218
-7
lines changed

processing.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,33 @@
11411141
this.z = z || 0;
11421142
}
11431143

1144+
PVector.fromAngle = function(angle, v) {
1145+
if (v === undef || v === null) {
1146+
v = new PVector();
1147+
}
1148+
v.x = Math.cos(angle);
1149+
v.y = Math.sin(angle);
1150+
return v;
1151+
}
1152+
1153+
PVector.random2D = function(v) {
1154+
return PVector.fromAngle(Math.random() * PConstants.TWO_PI, v);
1155+
}
1156+
1157+
PVector.random3D = function(v) {
1158+
var angle = Math.random() * PConstants.TWO_PI;
1159+
var vz = Math.random() * 2 - 1;
1160+
var mult = Math.sqrt(1 - vz * vz);
1161+
var vx = mult * Math.cos(angle);
1162+
var vy = mult * Math.sin(angle);
1163+
if (v === undef || v === null) {
1164+
v = new PVector(vx, vy, vz);
1165+
} else {
1166+
v.set(vx, vy, vz);
1167+
}
1168+
return v;
1169+
}
1170+
11441171
PVector.dist = function(v1, v2) {
11451172
return v1.dist(v2);
11461173
};
@@ -1161,6 +1188,13 @@
11611188
return Math.acos(v1.dot(v2) / (v1.mag() * v2.mag()));
11621189
};
11631190

1191+
PVector.lerp = function(v1, v2, amt) {
1192+
// non-static lerp mutates object, but this version returns a new vector
1193+
var retval = new PVector(v1.x, v1.y, v1.z);
1194+
retval.lerp(v2, amt);
1195+
return retval;
1196+
}
1197+
11641198
// Common vector operations for PVector
11651199
PVector.prototype = {
11661200
set: function(v, y, z) {
@@ -1183,6 +1217,24 @@
11831217
z = this.z;
11841218
return Math.sqrt(x * x + y * y + z * z);
11851219
},
1220+
magSq: function() {
1221+
var x = this.x,
1222+
y = this.y,
1223+
z = this.z;
1224+
return (x * x + y * y + z * z);
1225+
},
1226+
setMag: function(v_or_len, len) {
1227+
if (len === undef) {
1228+
len = v_or_len;
1229+
this.normalize();
1230+
this.mult(len);
1231+
} else {
1232+
var v = v_or_len;
1233+
v.normalize();
1234+
v.mult(len);
1235+
return v;
1236+
}
1237+
},
11861238
add: function(v, y, z) {
11871239
if (arguments.length === 1) {
11881240
this.x += v.x;
@@ -1227,6 +1279,13 @@
12271279
this.z /= v.z;
12281280
}
12291281
},
1282+
rotate: function(angle) {
1283+
var prev_x = this.x;
1284+
var c = Math.cos(angle);
1285+
var s = Math.sin(angle);
1286+
this.x = c * this.x - s * this.y;
1287+
this.y = s * prev_x + c * this.y;
1288+
},
12301289
dist: function(v) {
12311290
var dx = this.x - v.x,
12321291
dy = this.y - v.y,
@@ -1247,6 +1306,27 @@
12471306
z * v.x - v.z * x,
12481307
x * v.y - v.x * y);
12491308
},
1309+
lerp: function(v_or_x, amt_or_y, z, amt) {
1310+
// 3rd definition of lerp in this file...
1311+
function lerp_val(start, stop, amt) {
1312+
return start + (stop - start) * amt;
1313+
};
1314+
var x, y;
1315+
if (arguments.length === 2) {
1316+
// given vector and amt
1317+
amt = amt_or_y;
1318+
x = v_or_x.x;
1319+
y = v_or_x.y;
1320+
z = v_or_x.z;
1321+
} else {
1322+
// given x, y, z and amt
1323+
x = v_or_x;
1324+
y = amt_or_y;
1325+
}
1326+
this.x = lerp_val(this.x, x, amt);
1327+
this.y = lerp_val(this.y, y, amt);
1328+
this.z = lerp_val(this.z, z, amt);
1329+
},
12501330
normalize: function() {
12511331
var m = this.mag();
12521332
if (m > 0) {
@@ -1259,9 +1339,12 @@
12591339
this.mult(high);
12601340
}
12611341
},
1262-
heading2D: function() {
1342+
heading: function() {
12631343
return (-Math.atan2(-this.y, this.x));
12641344
},
1345+
heading2D: function() {
1346+
return this.heading();
1347+
},
12651348
toString: function() {
12661349
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
12671350
},

test/unit/PVector.pde

Lines changed: 134 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var round_tolerance = 0.00001;
2+
13
// SET
24
PVector v = new PVector(0.0, 0.0, 0.0);
35
v.set(20.0, 30.0, 40.0);
@@ -14,24 +16,28 @@ _checkEqual(v.array(), [20.0, 30.0, 40.0]);
1416
_checkEqual([v2.x, v2.y, v2.z], [10.0, 30.0, 40.0]);
1517

1618
// MAG
17-
_checkEqual(v.mag(), 53.851646, 0.00001);
19+
float m = 53.851648;
20+
_checkEqual(v.mag(), m, round_tolerance);
21+
22+
// MAGSQ
23+
_checkEqual(v.magSq(), m*m, Math.sqrt(round_tolerance));
1824

1925
PVector v3 = new PVector(10, 20, 2);
2026
PVector v4 = v3.get();
2127

2228
// NORMALIZE
2329
v3.normalize();
24-
_checkEqual([v3.x, v3.y, v3.z], [ 0.4454354, 0.8908708, 0.089087084 ], 0.00001);
30+
_checkEqual([v3.x, v3.y, v3.z], [ 0.4454354, 0.8908708, 0.089087084 ], round_tolerance);
2531

2632
// LIMIT
2733
v4.limit(5);
28-
_checkEqual([v4.x, v4.y, v4.z], [ 2.2271771, 4.4543543, 0.4454354 ], 0.00001);
34+
_checkEqual([v4.x, v4.y, v4.z], [ 2.2271771, 4.4543543, 0.4454354 ], round_tolerance);
2935

3036
// ANGLE BETWEEN
3137
PVector v1 = new PVector(10, 20);
3238
PVector v2 = new PVector(60, 80);
3339
float a = PVector.angleBetween(v1, v2);
34-
_checkEqual(degrees(a), 10.304827, 0.0001);
40+
_checkEqual(degrees(a), 10.304846, round_tolerance);
3541

3642
// CROSS
3743
PVector v1 = new PVector(10, 20, 2);
@@ -48,7 +54,7 @@ _checkEqual(d, 2200.0);
4854
PVector v1 = new PVector(10, 20, 0);
4955
PVector v2 = new PVector(60, 80, 0);
5056
float d = v1.dist(v2);
51-
_checkEqual(d, 78.10249, 0.0001);
57+
_checkEqual(d, 78.10249, round_tolerance);
5258

5359
// ADD
5460
PVector v1, v2;
@@ -78,7 +84,7 @@ v2.div(v1);
7884

7985
_checkEqual(v2.x, 0.625);
8086
_checkEqual(v2.y, 2.5);
81-
_checkEqual(v2.z, 0.6666667, 0.00001);
87+
_checkEqual(v2.z, 0.6666667, round_tolerance);
8288

8389
PVector v3 = PVector.div(v1, v2);
8490

@@ -109,3 +115,125 @@ PVector v2 = new PVector();
109115
v2.set(v1);
110116

111117
_checkEqual(v1.array(), v2.array());
118+
119+
// FROM ANGLE
120+
PVector v1 = PVector.fromAngle(HALF_PI);
121+
_checkEqual(v1.array(), [0, 1, 0], round_tolerance);
122+
123+
v1 = PVector.fromAngle(PI);
124+
_checkEqual(v1.array(), [-1, 0, 0], round_tolerance);
125+
126+
float over_root_2 = 1. / Math.sqrt(2);
127+
PVector.fromAngle(PI/4, v1); // specifying target
128+
_checkEqual(v1.array(), [over_root_2, over_root_2, 0], round_tolerance);
129+
130+
PVector v2 = PVector.fromAngle(3*PI/4, null); // should return new vector
131+
_checkEqual(v2.array(), [-over_root_2, over_root_2, 0], round_tolerance);
132+
133+
// RANDOM2D
134+
PVector v1 = PVector.random2D();
135+
PVector v2 = PVector.random2D();
136+
PVector v3 = new PVector();
137+
_checkEqual(v1.z, 0);
138+
_checkEqual(v2.z, 0);
139+
_checkNotEqual(v1.array(), v2.array());
140+
_checkNotEqual(v1.array(), v3.array());
141+
_checkNotEqual(v2.array(), v3.array());
142+
143+
PVector v4 = new PVector(v1.x, v1.y, v1.z);
144+
PVector.random2D(v4); // pass in target
145+
// should have changed from initial v1 values
146+
_checkNotEqual(v1.array(), v4.array());
147+
148+
PVector v5 = PVector.random2D(null); // should return new vector
149+
_checkEqual(v5.z, 0);
150+
_checkNotEqual(v4.array(), v5.array());
151+
152+
// RANDOM3D
153+
PVector v1 = PVector.random3D();
154+
PVector v2 = PVector.random3D();
155+
PVector v3 = new PVector();
156+
_checkNotEqual(v1.array(), v2.array());
157+
_checkNotEqual(v1.array(), v3.array());
158+
_checkNotEqual(v2.array(), v3.array());
159+
160+
PVector v4 = new PVector(v1.x, v1.y, v1.z);
161+
PVector.random3D(v4); // pass in target
162+
// should have changed from initial v1 values
163+
_checkNotEqual(v1.array(), v4.array());
164+
165+
PVector v5 = PVector.random3D(null); // should return new vector
166+
_checkNotEqual(v4.array(), v5.array());
167+
168+
// LERP
169+
PVector v1 = new PVector(10, 20, 30);
170+
PVector v2 = new PVector(20, 30, 40);
171+
PVector vlerp1 = PVector.lerp(v1, v2, 0);
172+
PVector vlerp2 = PVector.lerp(v1, v2, 0.5);
173+
PVector vlerp3 = PVector.lerp(v1, v2, 1);
174+
_checkEqual(vlerp1.array(), v1.array());
175+
_checkEqual(vlerp2.array(), [15, 25, 35]);
176+
_checkEqual(vlerp3.array(), v2.array());
177+
178+
// non-static version 1, another vector
179+
v1.lerp(v2, 0.5); // changes v1
180+
_checkEqual(vlerp2.array(), v1.array());
181+
182+
// non-static version 2, xyz values
183+
PVector v3 = new PVector(-v2.x, -v2.y, -v2.z);
184+
v3.lerp(v2.x, v2.y, v2.z, 0.5);
185+
_checkEqual(v3.array(), [0, 0, 0]);
186+
187+
// SETMAG
188+
void setMagTest(PVector v, float m) {
189+
v.setMag(m);
190+
_checkEqual(v.mag(), m, round_tolerance);
191+
}
192+
void setMagNoop(PVector v) {
193+
float[] a = v.array();
194+
setMagTest(v, v.mag());
195+
_checkEqual(a, v.array(), round_tolerance);
196+
}
197+
PVector v1 = new PVector(-1, 0, 0);
198+
setMagTest(v1, 10);
199+
_checkEqual(v1.array(), [-10, 0, 0]);
200+
201+
setMagNoop(new PVector(-0.05, 2013, 2.718), 10);
202+
setMagNoop(new PVector(578, 123, 432));
203+
setMagNoop(PVector.random3D());
204+
205+
// vector arg version
206+
void setMagTest2(PVector unused, PVector v, float m) {
207+
unused.setMag(v, m);
208+
_checkEqual(v.mag(), m, round_tolerance);
209+
}
210+
setMagTest2(v1, new PVector(-1, -1, -1), 1);
211+
setMagTest2(v1, new PVector(9812, 38, 1.65), 193);
212+
// according to p5 docs, giving setMag a null first argument should
213+
// cause it to create a new vector. however, this will have a
214+
// magnitude of 0, regardless of the second argument (this is even the
215+
// case in p5). that's probably not correct, so no test for it.
216+
217+
// ROTATE
218+
PVector v1 = new PVector(100, 0, 0);
219+
v1.rotate(HALF_PI);
220+
_checkEqual(v1.array(), [0, 100, 0], round_tolerance);
221+
v1.rotate(PI);
222+
_checkEqual(v1.array(), [0, -100, 0], round_tolerance);
223+
v1.rotate(-1.5*PI);
224+
_checkEqual(v1.array(), [100, 0, 0], round_tolerance);
225+
v1.rotate(QUARTER_PI);
226+
_checkEqual(v1.array(), [100*over_root_2, 100*over_root_2, 0], round_tolerance);
227+
228+
// HEADING
229+
void headingTest(float angle) {
230+
// angle needs to be between [-PI, PI]. otherwise, the numerical
231+
// values may be off by a multiple of 2PI.
232+
PVector v = PVector.fromAngle(angle);
233+
_checkEqual(v.heading(), angle, round_tolerance);
234+
}
235+
_checkEqual((new PVector(128, 0, 0)).heading(), 0);
236+
_checkEqual((new PVector(0, 0.1, 0)).heading(), HALF_PI);
237+
headingTest(Math.random() * TWO_PI - PI);
238+
headingTest(Math.random() * TWO_PI - PI);
239+
headingTest(Math.random() * TWO_PI - PI);

0 commit comments

Comments
 (0)