Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented roll function #7093

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';

rohanjulka19 marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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
Loading