@raetrakt – @glv’s solution is absolutely correct for a fixed-axis camera.
However, it seems that you may want your secondary rotation to be the plane of intersection created by your primary rotation. One approach to do this is a quaternion-based camera with relative motion input. For that please take a look at the p5.easyCam library, based on peasyCam.
For example, check out the Quick Start Ortho demo:
https://diwi.github.io/p5.EasyCam/examples/QuickStart_Ortho/
Note that, regardless, some of the basic feedback above still applies. If you want things to “always move right” etc. that cannot be true if a point rotates beyond the silhouette of the sphere – so your ranges (-PI, PI) are incorrect. Your inputs should range from -PI/4 to PI/4.
The other way is to actually translate to your plane of intersection, rotate, and draw. Here is an example of how that works:
https://editor.p5js.org/jeremydouglass/sketches/C8iRfwjx-
/* tumbleSphere - p5.js 2019-09 Jeremy Douglass
* - red is a normal sphere rotating on two axes
* - blue translates to the x point of rotation,
* then rotates in y round that cross-section.
* Use the blue/red overlap to see the difference.
*
* This sketch uses an orthographic camera to prevent
* perspective warping on the non-centered examples.
*
* See https://discourse.processing.org/t/3d-rotations-rotating-around-the-screen-axes/14239
*/
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
smooth();
noFill();
ortho(-width / 2, width / 2, -height / 2, height / 2, 0);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight, WEBGL);
}
function draw() {
background(255);
let x = map(mouseX, 0, width, -PI / 2, PI / 2);
let y = map(mouseY, 0, height, -PI / 2, PI / 2);
let r = width/6;
translate(-width/4, -height/4);
stroke(0,0,255);
tumbleSphere(x, y, r);
translate(width/4, height/4);
stroke(255, 0, 0);
axisSphere(x, y, r);
stroke(0,0,255);
tumbleSphere(x, y, r);
translate(width/4, -height/4);
stroke(255, 0, 0);
axisSphere(x, y, r);
}
function axisSphere(rx, ry, radius) {
ghostSphere(radius);
ellipse(0, 0, 2*radius, 2*radius);
push();
rotateY(rx);
rotateX(-ry);
push();
translate(0, 0, radius);
sphere(5);
pop();
ellipse(0, 0, radius * 2, radius * 2, 50);
rotateX(HALF_PI);
ellipse(0, 0, radius * 2, radius * 2, 50);
rotateY(HALF_PI);
ellipse(0, 0, radius * 2, radius * 2, 50);
pop();
}
function tumbleSphere(rx, ry, radius) {
ghostSphere(radius);
ellipse(0, 0, 2*radius, 2*radius);
push();
line(-radius, 0, 0, radius, 0, 0);
translate(sin(rx) * radius, 0, 0);
rotateY(HALF_PI);
ellipse(0, 0, radius*2 * cos(rx), radius*2 * cos(rx), 50);
rotateZ(-ry);
translate(-radius * cos(rx), 0, 0);
sphere(5);
pop();
}
function ghostSphere(radius){
push();
stroke(0,16);
sphere(radius);
pop();
}