Hi @Thomzoy,
Some general advice for 3D:
- Use
PMatrix3D
, a class
, instead of PMatrix
, an interface
.
- Processing’s unconventional world coordinate system makes even the simplest orientation problem a mess. Experiment with
camera
and perspective
or ortho
until you get a more conventional world coordinate system. Two conventions are Z+ as world up axis or Y+ as world up.
- Draw a transform for the world coordinates when debugging.
- Create an MCVE. (I.e., whether or not the ball is textured makes no difference to the rotation problem.)
Your rotating
function looks like a mix of two functions usually found in a affine transform matrix: rotateAngleAxis
and lookAt
. PMatrix3D
has an overload for rotate which supports an arbitrary axis. A lookAt
function takes a forward direction (or two points representing an origin and destination) and a reference to the world up direction, then constructs a matrix from the object’s local forward, right and up axes. So far as I know, it’s not in PMatrix3D
, but various tutorials to implement it are available throughout the web.
For the first example, I just used rotateZ
and rotateY
.
static final float SQRT_3_2 = sqrt(3.0) / 2.0;
PShape sphere;
int sphereDetail = 12;
float sphereSize = 150.0;
PMatrix3D sphereMat = new PMatrix3D();
float theta = 0.0;
float phi = 0.0;
PVector dir = new PVector();
float speed = 0.125;
void setup() {
size(512, 256, P3D);
ellipseMode(RADIUS);
smooth(8);
sphereDetail(sphereDetail);
// Create sphere.
sphere = createShape(SPHERE, sphereSize);
sphere.setFill(false);
sphere.setStrokeWeight(0.5);
sphere.setStroke(0xff202020);
}
void draw() {
float camDist = height * SQRT_3_2;
theta = frameCount * 0.01;
phi = TAU * mouseX / (float)(width - 1);
PVector.fromAngle(phi, dir);
dir.mult(frameCount * speed);
// Update sphere matrix.
sphereMat.reset();
sphereMat.translate(dir.x, dir.y, dir.z);
sphereMat.rotateZ(phi);
sphereMat.rotateY(theta);
// Apply sphere matrix to shape.
sphere.resetMatrix();
sphere.applyMatrix(sphereMat);
surface.setTitle(String.format("%.0f",
degrees(phi)));
background(0xffffffff);
// Draw camera with z-up world coordinates.
perspective(THIRD_PI, -width / (float)height,
0.01, 1000.0);
camera(camDist, -camDist, camDist,
0.0, 0.0, 0.0,
0.0, 0.0, -1.0);
drawArc(0.0, 0.0, 150.0, phi, 1.5);
drawTransform(125.0, 1.25);
shape(sphere);
}
void drawArc(float cx, float cy, float r, float t, float sw) {
noFill();
strokeWeight(sw);
stroke(0xffff7f00);
arc(cx, cy, r, r,
0.0, t, PIE);
}
void drawTransform(float len, float sw) {
strokeWeight(sw);
stroke(0xffff0000);
line(0.0, 0.0, 0.0, len, 0.0, 0.0);
stroke(0xff00ff00);
line(0.0, 0.0, 0.0, 0.0, len, 0.0);
stroke(0xff0000ff);
line(0.0, 0.0, 0.0, 0.0, 0.0, len);
}
A variant of the above with the lookAt function and orthographic projection:
static final float SQRT_3_2 = sqrt(3.0) / 2.0;
PShape sphere;
int sphereDetail = 12;
float sphereSize = 150.0;
PMatrix3D sphereMat = new PMatrix3D();
float theta = 0.0;
float phi = 0.0;
PVector dir = new PVector();
float speed = 0.125;
PVector worldUp = new PVector(0.0, 0.0, 1.0);
PVector xLocal = new PVector();
PVector yLocal = new PVector();
PVector zLocal = new PVector();
void setup() {
size(512, 256, P3D);
ellipseMode(RADIUS);
smooth(8);
sphereDetail(sphereDetail);
sphere = createShape(SPHERE, sphereSize);
sphere.setFill(false);
sphere.setStrokeWeight(0.5);
sphere.setStroke(0xff202020);
}
void draw() {
float camDist = height * SQRT_3_2;
float halfw = width * 0.5;
float halfh = height * 0.5;
theta = frameCount * 0.01;
phi = TAU * mouseX / (float)(width - 1);
PVector.fromAngle(phi, dir);
dir.mult(frameCount * speed);
sphereMat.reset();
lookAt(dir, PVector.add(dir, dir),
worldUp, sphereMat,
xLocal, yLocal, zLocal);
sphereMat.rotate(-theta, 1.0, 0.0, 0.0);
sphere.resetMatrix();
sphere.applyMatrix(sphereMat);
surface.setTitle(String.format("%.0f",
degrees(phi)));
background(0xffffffff);
ortho(halfw, -halfw, -halfh, halfh);
camera(camDist, -camDist, camDist,
0.0, 0.0, 0.0,
0.0, 0.0, -1.0);
drawArc(0.0, 0.0, 150.0, phi, 1.5);
drawWorldTransform(125.0, 1.25);
drawLocalTransform(75.0, 1.25, dir, xLocal, yLocal, zLocal);
shape(sphere);
}
void drawArc(float cx, float cy, float r, float t, float sw) {
noFill();
strokeWeight(sw);
stroke(0xffff7f00);
arc(cx, cy, r, r,
0.0, t, PIE);
}
void drawWorldTransform(float len, float sw) {
strokeWeight(sw);
stroke(0xffff0000);
line(0.0, 0.0, 0.0, len, 0.0, 0.0);
stroke(0xff00ff00);
line(0.0, 0.0, 0.0, 0.0, len, 0.0);
stroke(0xff0000ff);
line(0.0, 0.0, 0.0, 0.0, 0.0, len);
}
void drawLocalTransform(float len, float sw, PVector loc,
PVector xLocal, PVector yLocal, PVector zLocal) {
strokeWeight(sw);
stroke(0xffff0000);
line(loc.x, loc.y, loc.z,
loc.x + xLocal.x * len,
loc.y + xLocal.y * len,
loc.z + xLocal.z * len);
stroke(0xff00ff00);
line(loc.x, loc.y, loc.z,
loc.x + yLocal.x * len,
loc.y + yLocal.y * len,
loc.z + yLocal.z * len);
stroke(0xff0000ff);
line(loc.x, loc.y, loc.z,
loc.x + zLocal.x * len,
loc.y + zLocal.y * len,
loc.z + zLocal.z * len);
}
PMatrix3D lookAt(PVector origin, PVector destination,
PVector refUp, PMatrix3D out,
PVector xLocal, PVector yLocal, PVector zLocal) {
PVector.sub(destination, origin, yLocal);
yLocal.normalize();
PVector.cross(yLocal, refUp, xLocal);
xLocal.normalize();
PVector.cross(xLocal, yLocal, zLocal);
zLocal.normalize();
out.set(
xLocal.x, yLocal.x, zLocal.x, origin.x,
xLocal.y, yLocal.y, zLocal.y, origin.y,
xLocal.z, yLocal.z, zLocal.z, origin.z,
0.0, 0.0, 0.0, 1.0);
return out;
}
Note the orientation of the poles on Processing’s UV sphere depending on the coordinate systems used. That will influence how the texture appears on the final result.