From 6e9f2214d2430ef2cc683e857a8ce9d5c31a8a12 Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Thu, 1 Jun 2023 09:54:26 +0900
Subject: [PATCH 1/7] add option for free rotation
Add a "free" property to options. If this property is true, the direction of rotation will always match the direction of pointer movement.
I also added an explanation about it to the reference.
I've added comments to the example to explain how to use this.
---
src/webgl/interaction.js | 44 ++++++++++++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
index d9015b15f0..6c688530ee 100644
--- a/src/webgl/interaction.js
+++ b/src/webgl/interaction.js
@@ -29,6 +29,9 @@ import * as constants from '../core/constants';
* Setting this to true makes mobile interactions smoother by preventing
* accidental interactions with the page while orbiting. But if you're already
* doing it via css or want the default touch actions, consider setting it to false.
+ * free - Boolean, default value is false.
+ * Setting this to true will always rotate in the direction you move the mouse or touch pointer.
+ * Regarding zoom and move, both behave the same.
* @chainable
* @example
*
@@ -42,7 +45,11 @@ import * as constants from '../core/constants';
* }
* function draw() {
* background(200);
+ *
+ * // If you write here like orbitControl(1, 1, 1, {free: true})
+ * // instead of this, the behavior will change.
* orbitControl();
+ *
* rotateY(0.5);
* box(30, 50);
* }
@@ -105,6 +112,10 @@ p5.prototype.orbitControl = function(
this._setProperty('touchActionsDisabled', true);
}
+ // If option.free is true, it will always rotate freely in the direction
+ // the pointer moves. default value is false (normal behavior)
+ const { free = false } = options;
+
// get moved touches.
const movedTouches = [];
for (let i = 0; i < this.touches.length; i++) {
@@ -232,7 +243,16 @@ p5.prototype.orbitControl = function(
this._renderer.zoomVelocity += deltaRadius;
}
if (Math.abs(this._renderer.zoomVelocity) > 0.001) {
- this._renderer._curCamera._orbit(0, 0, this._renderer.zoomVelocity);
+ // if free, we use _orbitFree() instead of _orbit()
+ if (free) {
+ this._renderer._curCamera._orbitFree(
+ 0, 0, this._renderer.zoomVelocity
+ );
+ } else {
+ this._renderer._curCamera._orbit(
+ 0, 0, this._renderer.zoomVelocity
+ );
+ }
// In orthogonal projection, the scale does not change even if
// the distance to the gaze point is changed, so the projection matrix
// needs to be modified.
@@ -256,16 +276,24 @@ p5.prototype.orbitControl = function(
// accelerate rotate velocity
this._renderer.rotateVelocity.add(
deltaTheta * rotateAccelerationFactor,
- deltaPhi * rotateAccelerationFactor,
- 0
+ deltaPhi * rotateAccelerationFactor
);
}
if (this._renderer.rotateVelocity.magSq() > 0.000001) {
- this._renderer._curCamera._orbit(
- this._renderer.rotateVelocity.x,
- this._renderer.rotateVelocity.y,
- 0
- );
+ // if free, it will always rotate freely in the direction the pointer moves
+ if (free) {
+ this._renderer._curCamera._orbitFree(
+ -this._renderer.rotateVelocity.x,
+ this._renderer.rotateVelocity.y,
+ 0
+ );
+ } else {
+ this._renderer._curCamera._orbit(
+ this._renderer.rotateVelocity.x,
+ this._renderer.rotateVelocity.y,
+ 0
+ );
+ }
// damping
this._renderer.rotateVelocity.mult(damping);
} else {
From 9b7fd47dd41b8e233ff4c922aaddc00f9f24050f Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Thu, 1 Jun 2023 10:11:33 +0900
Subject: [PATCH 2/7] implement _orbitFree()
Implements _orbitFree(dx, dy, dRadius). The difference with _orbit() is that this _orbitFree() always rotates the camera in the direction of pointer movement.
---
src/webgl/p5.Camera.js | 78 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js
index 7a81de3172..c5a4a6cfef 100644
--- a/src/webgl/p5.Camera.js
+++ b/src/webgl/p5.Camera.js
@@ -1781,6 +1781,84 @@ p5.Camera = class Camera {
);
}
+ /**
+ * Orbits the camera about center point. For use with orbitControl().
+ * Unlike _orbit(), the direction of rotation always matches the direction of pointer movement.
+ * @method _orbitFree
+ * @private
+ * @param {Number} dx the x component of the rotation vector.
+ * @param {Number} dy the y component of the rotation vector.
+ * @param {Number} dRadius change in radius
+ */
+ _orbitFree(dx, dy, dRadius) {
+ // Calculate the vector and its magnitude from the center to the viewpoint
+ const diffX = this.eyeX - this.centerX;
+ const diffY = this.eyeY - this.centerY;
+ const diffZ = this.eyeZ - this.centerZ;
+ let camRadius = Math.hypot(diffX, diffY, diffZ);
+ // front vector. unit vector from center to eye.
+ const front = new p5.Vector(diffX, diffY, diffZ).normalize();
+ // up vector. camera's up vector.
+ const up = new p5.Vector(this.upX, this.upY, this.upZ);
+ // side vector. Right when viewed from the front. (like x-axis)
+ const side = new p5.Vector.cross(up, front).normalize();
+ // down vector. Bottom when viewed from the front. (like y-axis)
+ const down = new p5.Vector.cross(front, side);
+
+ // side vector and down vector are no longer used as-is.
+ // Create a vector representing the direction of rotation
+ // in the form cos(direction)*side + sin(direction)*down.
+ // Make the current side vector into this.
+ const directionAngle = Math.atan2(dy, dx);
+ down.mult(Math.sin(directionAngle));
+ side.mult(Math.cos(directionAngle)).add(down);
+ // The amount of rotation is the size of the vector (dx, dy).
+ const rotAngle = Math.sqrt(dx*dx + dy*dy);
+ // The vector that is orthogonal to both the front vector and
+ // the rotation direction vector is the rotation axis vector.
+ const axis = new p5.Vector.cross(front, side);
+
+ // update camRadius
+ camRadius *= Math.pow(10, dRadius);
+ // prevent zooming through the center:
+ if (camRadius < this.cameraNear) {
+ camRadius = this.cameraNear;
+ }
+ if (camRadius > this.cameraFar) {
+ camRadius = this.cameraFar;
+ }
+
+ // If the axis vector is likened to the z-axis, the front vector is
+ // the x-axis and the side vector is the y-axis. Rotate the up and front
+ // vectors respectively by thinking of them as rotations around the z-axis.
+
+ // Calculate the components by taking the dot product and
+ // calculate a rotation based on that.
+ const c = Math.cos(rotAngle);
+ const s = Math.sin(rotAngle);
+ const dotFront = up.dot(front);
+ const dotSide = up.dot(side);
+ const ux = dotFront * c + dotSide * s;
+ const uy = -dotFront * s + dotSide * c;
+ const uz = up.dot(axis);
+ up.x = ux * front.x + uy * side.x + uz * axis.x;
+ up.y = ux * front.y + uy * side.y + uz * axis.y;
+ up.z = ux * front.z + uy * side.z + uz * axis.z;
+ // We won't be using the side vector and the front vector anymore,
+ // so let's make the front vector into the vector from the center to the new eye.
+ side.mult(-s);
+ front.mult(c).add(side).mult(camRadius);
+
+ // it's complete. let's update camera.
+ this.camera(
+ front.x + this.centerX,
+ front.y + this.centerY,
+ front.z + this.centerZ,
+ this.centerX, this.centerY, this.centerZ,
+ up.x, up.y, up.z
+ );
+ }
+
/**
* Returns true if camera is currently attached to renderer.
* @method _isActive
From 390870acdbf9cb0009db021b26a327d75d0cbd80 Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Thu, 1 Jun 2023 11:27:10 +0900
Subject: [PATCH 3/7] Added unit test for matrices for _orbitFree()
Added unit test for matrices for _orbitFree()
---
test/unit/webgl/p5.Camera.js | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js
index 2d62fd1ec1..d68421e220 100644
--- a/test/unit/webgl/p5.Camera.js
+++ b/test/unit/webgl/p5.Camera.js
@@ -548,6 +548,42 @@ suite('p5.Camera', function() {
myCam._orbit(0, 0, -250);
assert.deepEqual(myCam.cameraMatrix.mat4, myCamCopy.cameraMatrix.mat4, 'deep equal is failing');
});
+ test('_orbitFree(1,0,0) sets correct matrix', function() {
+ var expectedMatrix = new Float32Array([
+ 0.5403022766113281, 0, -0.8414709568023682, 0,
+ 0, 1, 0, 0,
+ 0.8414709568023682, 0, 0.5403022766113281, 0,
+ -8.216248374992574e-7, 0, -86.6025390625, 1
+ ]);
+
+ myCam._orbitFree(1, 0, 0);
+
+ assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
+ });
+ test('_orbitFree(0,1,0) sets correct matrix', function() {
+ var expectedMatrix = new Float32Array([
+ 1, -2.8148363983860944e-17, -5.1525235865883254e-17, 0,
+ -2.8148363983860944e-17, 0.5403022766113281, -0.8414709568023682, 0,
+ 5.1525235865883254e-17, 0.8414709568023682, 0.5403022766113281, 0,
+ 1.8143673340160988e-22, -8.216248374992574e-7, -86.6025390625, 1
+ ]);
+
+ myCam._orbitFree(0, 1, 0);
+
+ assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
+ });
+ test('_orbitFree(0,0,1) sets correct matrix', function() {
+ var expectedMatrix = new Float32Array([
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, -866.025390625, 1
+ ]);
+
+ myCam._orbitFree(0, 0, 1);
+
+ assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
+ });
});
suite('Projection', function() {
From 6c653f270bea140a85b81caa782b1534efe6058f Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Thu, 1 Jun 2023 11:34:06 +0900
Subject: [PATCH 4/7] fix -0 to 0
fix -0 to 0
---
test/unit/webgl/p5.Camera.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js
index d68421e220..81ec6c300e 100644
--- a/test/unit/webgl/p5.Camera.js
+++ b/test/unit/webgl/p5.Camera.js
@@ -551,7 +551,7 @@ suite('p5.Camera', function() {
test('_orbitFree(1,0,0) sets correct matrix', function() {
var expectedMatrix = new Float32Array([
0.5403022766113281, 0, -0.8414709568023682, 0,
- 0, 1, 0, 0,
+ -0, 1, 0, 0,
0.8414709568023682, 0, 0.5403022766113281, 0,
-8.216248374992574e-7, 0, -86.6025390625, 1
]);
From 807c8f45f7afec6ea1fbdb3df7c6a85b393e34bf Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Thu, 1 Jun 2023 23:24:09 +0900
Subject: [PATCH 5/7] One rotation test
One rotation test
---
test/unit/webgl/p5.Camera.js | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js
index 81ec6c300e..e9572c5419 100644
--- a/test/unit/webgl/p5.Camera.js
+++ b/test/unit/webgl/p5.Camera.js
@@ -584,6 +584,22 @@ suite('p5.Camera', function() {
assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
});
+ test('Rotate camera 360° with _orbitFree() returns it to its original position', function() {
+ // Rotate the camera 360 degrees in any direction using _orbitFree()
+ // and it will return to its original state.
+ myCam.camera(100, 100, 100, 0, 0, 0, 1, 2, 3);
+ var myCamCopy = myCam.copy();
+ // Performing 200 rotations of Math.PI*0.01 makes exactly one rotation.
+ // However, we test in a slightly slanted direction instead of parallel with axis.
+ for (let i = 0; i < 200; i++) {
+ myCamCopy._orbitFree(Math.PI * 0.006, Math.PI * 0.008, 0);
+ }
+ for (let i = 0; i < myCamCopy.cameraMatrix.mat4.length; i++) {
+ expect(
+ myCamCopy.cameraMatrix.mat4[i]).to.be.closeTo(
+ myCam.cameraMatrix.mat4[i], 0.001);
+ }
+ });
});
suite('Projection', function() {
From 3a5893e97aa1830ee9df5710059ad2bc135e2fa0 Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Sat, 3 Jun 2023 02:52:59 +0900
Subject: [PATCH 6/7] more detailed comments, changed option name.
Changed the content of the document to better show camera details.
Also, since this new option is only for rotation operations, I thought "freeRotation" would be more appropriate than "free".
---
src/webgl/interaction.js | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
index 6c688530ee..16bee29ed5 100644
--- a/src/webgl/interaction.js
+++ b/src/webgl/interaction.js
@@ -29,9 +29,12 @@ import * as constants from '../core/constants';
* Setting this to true makes mobile interactions smoother by preventing
* accidental interactions with the page while orbiting. But if you're already
* doing it via css or want the default touch actions, consider setting it to false.
- * free - Boolean, default value is false.
- * Setting this to true will always rotate in the direction you move the mouse or touch pointer.
- * Regarding zoom and move, both behave the same.
+ * freeRotation - Boolean, default value is false.
+ * By default, horizontal movement of the mouse or touch pointer rotates the camera
+ * around the y-axis, and vertical movement rotates the camera around the x-axis.
+ * But if setting this option to true, the camera always rotates in the direction
+ * the pointer is moving. For zoom and move, the behavior is the same regardless of
+ * true/false.
* @chainable
* @example
*
@@ -46,9 +49,10 @@ import * as constants from '../core/constants';
* function draw() {
* background(200);
*
- * // If you write here like orbitControl(1, 1, 1, {free: true})
- * // instead of this, the behavior will change.
+ * // If you execute the line commented out instead of next line, the direction of rotation
+ * // will be the direction the mouse or touch pointer moves, not around the X or Y axis.
* orbitControl();
+ * // orbitControl(1, 1, 1, {freeRotation: true});
*
* rotateY(0.5);
* box(30, 50);
@@ -112,9 +116,9 @@ p5.prototype.orbitControl = function(
this._setProperty('touchActionsDisabled', true);
}
- // If option.free is true, it will always rotate freely in the direction
+ // If option.freeRotation is true, the camera always rotates freely in the direction
// the pointer moves. default value is false (normal behavior)
- const { free = false } = options;
+ const { freeRotation = false } = options;
// get moved touches.
const movedTouches = [];
@@ -243,8 +247,8 @@ p5.prototype.orbitControl = function(
this._renderer.zoomVelocity += deltaRadius;
}
if (Math.abs(this._renderer.zoomVelocity) > 0.001) {
- // if free, we use _orbitFree() instead of _orbit()
- if (free) {
+ // if freeRotation is true, we use _orbitFree() instead of _orbit()
+ if (freeRotation) {
this._renderer._curCamera._orbitFree(
0, 0, this._renderer.zoomVelocity
);
@@ -280,8 +284,8 @@ p5.prototype.orbitControl = function(
);
}
if (this._renderer.rotateVelocity.magSq() > 0.000001) {
- // if free, it will always rotate freely in the direction the pointer moves
- if (free) {
+ // if freeRotation is true, the camera always rotates freely in the direction the pointer moves
+ if (freeRotation) {
this._renderer._curCamera._orbitFree(
-this._renderer.rotateVelocity.x,
this._renderer.rotateVelocity.y,
From 11b9923dd947b070a240649d64eecc7a3a74fba9 Mon Sep 17 00:00:00 2001
From: INARI_DARKFOX <39549290+inaridarkfox4231@users.noreply.github.com>
Date: Sun, 4 Jun 2023 10:03:07 +0900
Subject: [PATCH 7/7] use forEach instead of for
In proposal of #6173, it was rewritten using forEach , so I rewrite it.
---
src/webgl/interaction.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
index 16bee29ed5..666e9a07be 100644
--- a/src/webgl/interaction.js
+++ b/src/webgl/interaction.js
@@ -122,10 +122,9 @@ p5.prototype.orbitControl = function(
// get moved touches.
const movedTouches = [];
- for (let i = 0; i < this.touches.length; i++) {
- const curTouch = this.touches[i];
- for (let k = 0; k < this._renderer.prevTouches.length; k++) {
- const prevTouch = this._renderer.prevTouches[k];
+
+ this.touches.forEach(curTouch => {
+ this._renderer.prevTouches.forEach(prevTouch => {
if (curTouch.id === prevTouch.id) {
const movedTouch = {
x: curTouch.x,
@@ -135,8 +134,9 @@ p5.prototype.orbitControl = function(
};
movedTouches.push(movedTouch);
}
- }
- }
+ });
+ });
+
this._renderer.prevTouches = this.touches;
// The idea of using damping is based on the following website. thank you.