Skip to content

Commit 3879ee6

Browse files
authored
Merge pull request #7093 from rohanjulka19/roll-fn
Implemented roll function
2 parents eb61f7a + 0636f81 commit 3879ee6

File tree

5 files changed

+290
-2
lines changed

5 files changed

+290
-2
lines changed

src/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import './webgl/p5.Camera';
9191
import './webgl/p5.DataArray';
9292
import './webgl/p5.Geometry';
9393
import './webgl/p5.Matrix';
94+
import './webgl/p5.Quat';
9495
import './webgl/p5.RendererGL.Immediate';
9596
import './webgl/p5.RendererGL';
9697
import './webgl/p5.RendererGL.Retained';

src/math/p5.Vector.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,5 +3887,33 @@ p5.Vector = class {
38873887
}
38883888
return v.equals(v2);
38893889
}
3890-
};
3891-
export default p5.Vector;
3890+
3891+
3892+
/**
3893+
* Replaces the components of a <a href="#/p5.Vector">p5.Vector</a> that are very close to zero with zero.
3894+
*
3895+
* In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented.
3896+
* This can make it hard to check if a number is zero, as it may be close but not exactly zero.
3897+
* This method rounds very close numbers to zero to make those checks easier
3898+
*
3899+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
3900+
*
3901+
* @method clampToZero
3902+
* @return {p5.Vector} with components very close to zero replaced with zero.
3903+
* @chainable
3904+
*/
3905+
clampToZero() {
3906+
this.x = this._clampToZero(this.x);
3907+
this.y = this._clampToZero(this.y);
3908+
this.z = this._clampToZero(this.z);
3909+
return this;
3910+
}
3911+
3912+
/**
3913+
* Helper function for clampToZero
3914+
* @private
3915+
*/
3916+
_clampToZero(val) {
3917+
return Math.abs((val||0) - 0) <= Number.EPSILON ? 0 : val;
3918+
}
3919+
};export default p5.Vector;

src/webgl/p5.Camera.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,84 @@ p5.Camera = class Camera {
24602460
);
24612461
}
24622462

2463+
/**
2464+
* Rotates the camera in a clockwise/counter-clockwise direction.
2465+
*
2466+
* Rolling rotates the camera without changing its orientation. The rotation
2467+
* happens in the camera’s "local" space.
2468+
*
2469+
* The parameter, `angle`, is the angle the camera should rotate. Passing a
2470+
* positive angle, as in `myCamera.roll(0.001)`, rotates the camera in counter-clockwise direction.
2471+
* Passing a negative angle, as in `myCamera.roll(-0.001)`, rotates the
2472+
* camera in clockwise direction.
2473+
*
2474+
* Note: Angles are interpreted based on the current
2475+
* <a href="#/p5/angleMode">angleMode()</a>.
2476+
*
2477+
* @method roll
2478+
* @param {Number} angle amount to rotate camera in current
2479+
* <a href="#/p5/angleMode">angleMode</a> units.
2480+
* @example
2481+
* <div>
2482+
* <code>
2483+
* let cam;
2484+
* let delta = 0.01;
2485+
*
2486+
* function setup() {
2487+
* createCanvas(100, 100, WEBGL);
2488+
* normalMaterial();
2489+
* // Create a p5.Camera object.
2490+
* cam = createCamera();
2491+
* }
2492+
*
2493+
* function draw() {
2494+
* background(200);
2495+
*
2496+
* // Roll camera according to angle 'delta'
2497+
* cam.roll(delta);
2498+
*
2499+
* translate(0, 0, 0);
2500+
* box(20);
2501+
* translate(0, 25, 0);
2502+
* box(20);
2503+
* translate(0, 26, 0);
2504+
* box(20);
2505+
* translate(0, 27, 0);
2506+
* box(20);
2507+
* translate(0, 28, 0);
2508+
* box(20);
2509+
* translate(0,29, 0);
2510+
* box(20);
2511+
* translate(0, 30, 0);
2512+
* box(20);
2513+
* }
2514+
* </code>
2515+
* </div>
2516+
*
2517+
* @alt
2518+
* camera view rotates in counter clockwise direction with vertically stacked boxes in front of it.
2519+
*/
2520+
roll(amount) {
2521+
const local = this._getLocalAxes();
2522+
const axisQuaternion = p5.Quat.fromAxisAngle(
2523+
this._renderer._pInst._toRadians(amount),
2524+
local.z[0], local.z[1], local.z[2]);
2525+
// const upQuat = new p5.Quat(0, this.upX, this.upY, this.upZ);
2526+
const newUpVector = axisQuaternion.rotateVector(
2527+
new p5.Vector(this.upX, this.upY, this.upZ));
2528+
this.camera(
2529+
this.eyeX,
2530+
this.eyeY,
2531+
this.eyeZ,
2532+
this.centerX,
2533+
this.centerY,
2534+
this.centerZ,
2535+
newUpVector.x,
2536+
newUpVector.y,
2537+
newUpVector.z
2538+
);
2539+
}
2540+
24632541
/**
24642542
* Rotates the camera left and right.
24652543
*

src/webgl/p5.Quat.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @module Math
3+
* @submodule Quaternion
4+
*/
5+
6+
import p5 from '../core/main';
7+
8+
/**
9+
* A class to describe a Quaternion
10+
* for vector rotations in the p5js webgl renderer.
11+
* Please refer the following link for details on the implementation
12+
* https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
13+
* @class p5.Quat
14+
* @constructor
15+
* @param {Number} [w] Scalar part of the quaternion
16+
* @param {Number} [x] x component of imaginary part of quaternion
17+
* @param {Number} [y] y component of imaginary part of quaternion
18+
* @param {Number} [z] z component of imaginary part of quaternion
19+
* @private
20+
*/
21+
p5.Quat = class {
22+
constructor(w, x, y, z) {
23+
this.w = w;
24+
this.vec = new p5.Vector(x, y, z);
25+
}
26+
27+
/**
28+
* Returns a Quaternion for the
29+
* axis angle representation of the rotation
30+
*
31+
* @method fromAxisAngle
32+
* @param {Number} [angle] Angle with which the points needs to be rotated
33+
* @param {Number} [x] x component of the axis vector
34+
* @param {Number} [y] y component of the axis vector
35+
* @param {Number} [z] z component of the axis vector
36+
* @chainable
37+
*/
38+
static fromAxisAngle(angle, x, y, z) {
39+
const w = Math.cos(angle/2);
40+
const vec = new p5.Vector(x, y, z).normalize().mult(Math.sin(angle/2));
41+
return new p5.Quat(w, vec.x, vec.y, vec.z);
42+
}
43+
44+
conjugate() {
45+
return new p5.Quat(this.w, -this.vec.x, -this.vec.y, -this.vec.z);
46+
}
47+
48+
/**
49+
* Multiplies a quaternion with other quaternion.
50+
* @method mult
51+
* @param {p5.Quat} [quat] quaternion to multiply with the quaternion calling the method.
52+
* @chainable
53+
*/
54+
multiply(quat) {
55+
/* eslint-disable max-len */
56+
return new p5.Quat(
57+
this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z - quat.vec.z,
58+
this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
59+
this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
60+
this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w
61+
);
62+
/* eslint-enable max-len */
63+
}
64+
65+
/**
66+
* This is similar to quaternion multiplication
67+
* but when multipying vector with quaternion
68+
* the multiplication can be simplified to the below formula.
69+
* This was taken from the below stackexchange link
70+
* https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion/50545#50545
71+
* @param {p5.Vector} [p] vector to rotate on the axis quaternion
72+
* @returns
73+
*/
74+
rotateVector(p) {
75+
return new p5.Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) )
76+
.add( p5.Vector.mult( this.vec, 2 * p.dot(this.vec) ) )
77+
.add( p5.Vector.mult( this.vec, 2 * this.w ).cross( p ) )
78+
.clampToZero();
79+
}
80+
81+
/**
82+
* Rotates the Quaternion by the quaternion passed
83+
* which contains the axis of roation and angle of rotation
84+
*
85+
* @method rotateBy
86+
* @param {p5.Quat} [axesQuat] axis quaternion which contains
87+
* the axis of rotation and angle of rotation
88+
* @chainable
89+
*/
90+
rotateBy(axesQuat) {
91+
return axesQuat.multiply(this).multiply(axesQuat.conjugate()).
92+
vec.clampToZero();
93+
}
94+
};
95+
96+
export default p5.Quat;

test/unit/webgl/p5.Camera.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { HALF_PI } from '../../../src/core/constants';
2+
13
suite('p5.Camera', function() {
24
var myp5;
35
var myCam;
@@ -194,6 +196,69 @@ suite('p5.Camera', function() {
194196
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
195197
});
196198

199+
test('Roll() with positive parameter sets correct Matrix w/o \
200+
changing eyeXYZ', function() {
201+
var orig = getVals(myCam);
202+
203+
var expectedMatrix = new Float32Array([
204+
0, -1, 0, 0,
205+
1, 0, 0, 0,
206+
0, 0, 1, 0,
207+
0, 0, -86.6025390625, 1
208+
]);
209+
210+
myCam.roll(HALF_PI);
211+
212+
assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
213+
214+
assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
215+
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
216+
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
217+
});
218+
219+
test('Roll() with negative parameter sets correct matrix w/o \
220+
changing eyeXYZ', function() {
221+
var orig = getVals(myCam);
222+
223+
var expectedMatrix = new Float32Array([
224+
0, 1, 0, 0,
225+
-1, 0, 0, 0,
226+
0, 0, 1, 0,
227+
0, 0, -86.6025390625, 1
228+
]);
229+
230+
myCam.tilt(HALF_PI);
231+
232+
assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
233+
234+
assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
235+
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
236+
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
237+
});
238+
239+
test('Roll(0) sets correct matrix w/o changing upXYZ and eyeXYZ', function() {
240+
var orig = getVals(myCam);
241+
242+
var expectedMatrix = new Float32Array([
243+
1, 0, 0, 0,
244+
0, 1, 0, 0,
245+
0, 0, 1, 0,
246+
0, 0, -86.6025390625, 1
247+
]);
248+
249+
myCam.roll(0);
250+
251+
assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
252+
253+
assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
254+
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
255+
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
256+
257+
assert.strictEqual(myCam.upX, orig.ux, 'up X pos changed');
258+
assert.strictEqual(myCam.upY, orig.uy, 'up Y pos changed');
259+
assert.strictEqual(myCam.upZ, orig.uz, 'up Z pos changed');
260+
});
261+
197262
test('LookAt() should set centerXYZ without changing eyeXYZ or \
198263
upXYZ', function() {
199264
var orig = getVals(myCam);
@@ -266,6 +331,26 @@ suite('p5.Camera', function() {
266331
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
267332
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
268333
});
334+
335+
test('Roll() with positive parameter sets correct Matrix w/o \
336+
changing eyeXYZ', function() {
337+
var orig = getVals(myCam);
338+
339+
var expectedMatrix = new Float32Array([
340+
0, -1, 0, 0,
341+
1, 0, 0, 0,
342+
0, 0, 1, 0,
343+
0, 0, -86.6025390625, 1
344+
]);
345+
346+
myCam.roll(90);
347+
348+
assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
349+
350+
assert.strictEqual(myCam.eyeX, orig.eyeX, 'eye X pos changed');
351+
assert.strictEqual(myCam.eyeY, orig.eyeY, 'eye Y pos changed');
352+
assert.strictEqual(myCam.eyeZ, orig.eyeZ, 'eye Z pos changed');
353+
});
269354
});
270355

271356
suite('Position / Orientation', function() {

0 commit comments

Comments
 (0)