Skip to content

Commit

Permalink
Merge pull request #7093 from rohanjulka19/roll-fn
Browse files Browse the repository at this point in the history
Implemented roll function
  • Loading branch information
davepagurek authored Jun 25, 2024
2 parents eb61f7a + 0636f81 commit 3879ee6
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import './webgl/p5.Camera';
import './webgl/p5.DataArray';
import './webgl/p5.Geometry';
import './webgl/p5.Matrix';
import './webgl/p5.Quat';
import './webgl/p5.RendererGL.Immediate';
import './webgl/p5.RendererGL';
import './webgl/p5.RendererGL.Retained';
Expand Down
32 changes: 30 additions & 2 deletions src/math/p5.Vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3887,5 +3887,33 @@ p5.Vector = class {
}
return v.equals(v2);
}
};
export default p5.Vector;


/**
* Replaces the components of a <a href="#/p5.Vector">p5.Vector</a> that are very close to zero with zero.
*
* In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented.
* This can make it hard to check if a number is zero, as it may be close but not exactly zero.
* This method rounds very close numbers to zero to make those checks easier
*
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
*
* @method clampToZero
* @return {p5.Vector} with components very close to zero replaced with zero.
* @chainable
*/
clampToZero() {
this.x = this._clampToZero(this.x);
this.y = this._clampToZero(this.y);
this.z = this._clampToZero(this.z);
return this;
}

/**
* Helper function for clampToZero
* @private
*/
_clampToZero(val) {
return Math.abs((val||0) - 0) <= Number.EPSILON ? 0 : val;
}
};export default p5.Vector;
78 changes: 78 additions & 0 deletions src/webgl/p5.Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -2460,6 +2460,84 @@ p5.Camera = class Camera {
);
}

/**
* Rotates the camera in a clockwise/counter-clockwise direction.
*
* Rolling rotates the camera without changing its orientation. The rotation
* happens in the camera’s "local" space.
*
* The parameter, `angle`, is the angle the camera should rotate. Passing a
* positive angle, as in `myCamera.roll(0.001)`, rotates the camera in counter-clockwise direction.
* Passing a negative angle, as in `myCamera.roll(-0.001)`, rotates the
* camera in clockwise direction.
*
* Note: Angles are interpreted based on the current
* <a href="#/p5/angleMode">angleMode()</a>.
*
* @method roll
* @param {Number} angle amount to rotate camera in current
* <a href="#/p5/angleMode">angleMode</a> units.
* @example
* <div>
* <code>
* let cam;
* let delta = 0.01;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
* normalMaterial();
* // Create a p5.Camera object.
* cam = createCamera();
* }
*
* function draw() {
* background(200);
*
* // Roll camera according to angle 'delta'
* cam.roll(delta);
*
* translate(0, 0, 0);
* box(20);
* translate(0, 25, 0);
* box(20);
* translate(0, 26, 0);
* box(20);
* translate(0, 27, 0);
* box(20);
* translate(0, 28, 0);
* box(20);
* translate(0,29, 0);
* box(20);
* translate(0, 30, 0);
* box(20);
* }
* </code>
* </div>
*
* @alt
* camera view rotates in counter clockwise direction with vertically stacked boxes in front of it.
*/
roll(amount) {
const local = this._getLocalAxes();
const axisQuaternion = p5.Quat.fromAxisAngle(
this._renderer._pInst._toRadians(amount),
local.z[0], local.z[1], local.z[2]);
// const upQuat = new p5.Quat(0, this.upX, this.upY, this.upZ);
const newUpVector = axisQuaternion.rotateVector(
new p5.Vector(this.upX, this.upY, this.upZ));
this.camera(
this.eyeX,
this.eyeY,
this.eyeZ,
this.centerX,
this.centerY,
this.centerZ,
newUpVector.x,
newUpVector.y,
newUpVector.z
);
}

/**
* Rotates the camera left and right.
*
Expand Down
96 changes: 96 additions & 0 deletions src/webgl/p5.Quat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @module Math
* @submodule Quaternion
*/

import p5 from '../core/main';

/**
* A class to describe a Quaternion
* for vector rotations in the p5js webgl renderer.
* Please refer the following link for details on the implementation
* https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
* @class p5.Quat
* @constructor
* @param {Number} [w] Scalar part of the quaternion
* @param {Number} [x] x component of imaginary part of quaternion
* @param {Number} [y] y component of imaginary part of quaternion
* @param {Number} [z] z component of imaginary part of quaternion
* @private
*/
p5.Quat = class {
constructor(w, x, y, z) {
this.w = w;
this.vec = new p5.Vector(x, y, z);
}

/**
* Returns a Quaternion for the
* axis angle representation of the rotation
*
* @method fromAxisAngle
* @param {Number} [angle] Angle with which the points needs to be rotated
* @param {Number} [x] x component of the axis vector
* @param {Number} [y] y component of the axis vector
* @param {Number} [z] z component of the axis vector
* @chainable
*/
static fromAxisAngle(angle, x, y, z) {
const w = Math.cos(angle/2);
const vec = new p5.Vector(x, y, z).normalize().mult(Math.sin(angle/2));
return new p5.Quat(w, vec.x, vec.y, vec.z);
}

conjugate() {
return new p5.Quat(this.w, -this.vec.x, -this.vec.y, -this.vec.z);
}

/**
* Multiplies a quaternion with other quaternion.
* @method mult
* @param {p5.Quat} [quat] quaternion to multiply with the quaternion calling the method.
* @chainable
*/
multiply(quat) {
/* eslint-disable max-len */
return new p5.Quat(
this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z - quat.vec.z,
this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w
);
/* eslint-enable max-len */
}

/**
* This is similar to quaternion multiplication
* but when multipying vector with quaternion
* the multiplication can be simplified to the below formula.
* This was taken from the below stackexchange link
* https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion/50545#50545
* @param {p5.Vector} [p] vector to rotate on the axis quaternion
* @returns
*/
rotateVector(p) {
return new p5.Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) )
.add( p5.Vector.mult( this.vec, 2 * p.dot(this.vec) ) )
.add( p5.Vector.mult( this.vec, 2 * this.w ).cross( p ) )
.clampToZero();
}

/**
* Rotates the Quaternion by the quaternion passed
* which contains the axis of roation and angle of rotation
*
* @method rotateBy
* @param {p5.Quat} [axesQuat] axis quaternion which contains
* the axis of rotation and angle of rotation
* @chainable
*/
rotateBy(axesQuat) {
return axesQuat.multiply(this).multiply(axesQuat.conjugate()).
vec.clampToZero();
}
};

export default p5.Quat;
85 changes: 85 additions & 0 deletions test/unit/webgl/p5.Camera.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HALF_PI } from '../../../src/core/constants';

suite('p5.Camera', function() {
var myp5;
var myCam;
Expand Down Expand Up @@ -194,6 +196,69 @@ suite('p5.Camera', function() {
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
});

test('Roll() with positive parameter sets correct Matrix w/o \
changing eyeXYZ', function() {
var orig = getVals(myCam);

var expectedMatrix = new Float32Array([
0, -1, 0, 0,
1, 0, 0, 0,
0, 0, 1, 0,
0, 0, -86.6025390625, 1
]);

myCam.roll(HALF_PI);

assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);

assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
});

test('Roll() with negative parameter sets correct matrix w/o \
changing eyeXYZ', function() {
var orig = getVals(myCam);

var expectedMatrix = new Float32Array([
0, 1, 0, 0,
-1, 0, 0, 0,
0, 0, 1, 0,
0, 0, -86.6025390625, 1
]);

myCam.tilt(HALF_PI);

assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);

assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
});

test('Roll(0) sets correct matrix w/o changing upXYZ and eyeXYZ', function() {
var orig = getVals(myCam);

var expectedMatrix = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, -86.6025390625, 1
]);

myCam.roll(0);

assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);

assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');

assert.strictEqual(myCam.upX, orig.ux, 'up X pos changed');
assert.strictEqual(myCam.upY, orig.uy, 'up Y pos changed');
assert.strictEqual(myCam.upZ, orig.uz, 'up Z pos changed');
});

test('LookAt() should set centerXYZ without changing eyeXYZ or \
upXYZ', function() {
var orig = getVals(myCam);
Expand Down Expand Up @@ -266,6 +331,26 @@ suite('p5.Camera', function() {
assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
});

test('Roll() with positive parameter sets correct Matrix w/o \
changing eyeXYZ', function() {
var orig = getVals(myCam);

var expectedMatrix = new Float32Array([
0, -1, 0, 0,
1, 0, 0, 0,
0, 0, 1, 0,
0, 0, -86.6025390625, 1
]);

myCam.roll(90);

assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);

assert.strictEqual(myCam.eyeX, orig.eyeX, 'eye X pos changed');
assert.strictEqual(myCam.eyeY, orig.eyeY, 'eye Y pos changed');
assert.strictEqual(myCam.eyeZ, orig.eyeZ, 'eye Z pos changed');
});
});

suite('Position / Orientation', function() {
Expand Down

0 comments on commit 3879ee6

Please sign in to comment.