# 3D Textured rolling ball

Hi !

I’m trying to do a (seemingly) easy thing but I am having a lot of trouble doing it.
I want to simulate a sphere rolling on a plane, with a texture on it.
To do so I computed the vector around which the sphere should rotate at each frame : it is the cross product of the velocity vector and the vector pointing upward.
After that I created the 4*4 Matrix representing the 3D rotation of the sphere around that axis (of a small angle).
But now I have trouble applying this matrix to the sphere.
Any help would be greatly appreciated (I’m quite new to processing !)
Thanks

EDIT : I posted the code below

Here is the code I have so far :

``````float depth = 1000; // Camera parameter
float boxWidth = 1000;
float boxThick = 16;
float vx = 0;
float vz = 0;
PImage img;
PShape globe;

PVector y0 = new PVector(0,1,0);

Mover mover;

void settings() {
size(1500,1500, P3D);
mover = new Mover();
}

void setup() {
noStroke();
globe.setStroke(false);
globe.setTexture(img);

}

void draw() {
camera(0, -0.5*depth, depth, 0, 0, 0, 0, 1, 0);
directionalLight(50, 100, 125, 0, 1, 0);
ambientLight(102, 102, 102);
background(200);
pushMatrix();
box(boxWidth,boxThick,boxWidth);
popMatrix();
lights();

mover.update();
mover.checkEdges();
mover.display();

}

void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
vx += -0.1;
}
else if (keyCode == DOWN) {
vx += 0.1;
}
else if (keyCode == RIGHT) {
vz += 0.1;
}
else if (keyCode == LEFT) {
vz += -0.1;
}
}
}

``````

The Mover class :

``````float reboundConstant = 1;
float angle = 0;

PMatrix result = null;

class Mover {

PVector location;
PVector velocity;
PVector force;

Mover() {
location = new PVector(0, 0, 0);
velocity = new PVector(0, 0, 0);
}

void update() {
PVector v = new PVector(vz,0,vx);
velocity.y = 0;

}

void display() {
noStroke();
rotating();
shape(globe);
}

void rotating() {
result = getMatrix();
PVector cross = y0.cross(velocity);
cross.y = 0;
cross.normalize();
float ux = cross.x;
float uy = 0;
float uz = -cross.z;
float c = cos(angle);
float s = sin(angle);

float r11 = ux*ux*(1-c)+c;
float r12 = ux*uy*(1-c)-uz*s;
float r13 = ux*uz*(1-c)+uy*s;
float r21 = ux*uy*(1-c)+uz*s;
float r22 = uy*uy*(1-c)+c;
float r23 = uy*uz*(1-c)-ux*s;
float r31 = ux*uz*(1-c)-uy*s;
float r32 = uy*uz*(1-c)+ux*s;
float r33 = uz*uz*(1-c)+c;

result.apply(r11,r12,r13,0,r21,r22,r23,0,r31,r32,r33,0,0,0,0,1);
setMatrix(result);
applyMatrix(result);
pushMatrix();
shape(globe);
popMatrix();

}

void checkEdges() {
if (location.x > 0.5*boxWidth - radius) {
velocity.x *= -reboundConstant;
}
else if (location.x < -0.5*boxWidth + radius) {
velocity.x *= -reboundConstant;
}
if (location.z > 0.5*boxWidth - radius) {
velocity.z *= -reboundConstant;
}
else if (location.z < -0.5*boxWidth + radius) {
velocity.z *= -reboundConstant;
}
}
}
``````

The rotating() bit is not working at all…

1 Like

Hi @Thomzoy,

• 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);
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);
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();
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.

2 Likes