I wrote this today for a project I’m working on. Maybe useful for someone?
The idea is to drag objects in 3D to rotate them.
(watch video)
// A collection of trackballs. They can be rotated and
// they keep the rotation info.
ArrayList<TrackBall> tbs = new ArrayList<TrackBall>();
// Which of the objects is closer to the mouse, to rotate that one
TrackBall closest = null;
void setup() {
size(600, 600, P3D);
noStroke();
// Create a bunch of TrackBalls
for (int i=0; i<30; i++) {
TrackBall tb = new TrackBall();
tb.setPos(random(600), random(600));
tbs.add(tb);
}
}
void draw() {
background(#220A27);
lights();
// Draw cubes using the rotation information stored
// in the trackballs
for (int i=0; i<tbs.size(); i++) {
TrackBall tb = tbs.get(i);
pushMatrix();
translate(tb.pos.x, tb.pos.y);
applyMatrix(tb.rotationMat);
fill(lerpColor(#0FB5FF, #E8E37C, i / (float)tbs.size()));
box(50 + 20 * (i%5));
popMatrix();
}
}
// When pressing the mouse find which TrackBall is closer to
// the mouse
void mousePressed() {
closest = null;
float distance = 999999;
for (TrackBall tb : tbs) {
float d = dist(mouseX, mouseY, tb.pos.x, tb.pos.y);
if(d < distance) {
distance = d;
closest = tb;
}
}
}
// When dragging the mouse tell about the dragging to
// the closest TrackBall, so it updates its internal
// rotation information.
void mouseDragged() {
if(closest != null) {
closest.drag(pmouseX, pmouseY, mouseX, mouseY);
}
}
// Inspired by
// https://stackoverflow.com/questions/11314384/virtual-trackball-implementation
// Using Toxiclibs for the Quaternion.
import toxi.geom.*;
// This TrackBall class allows rotating 3D objects by dragging.
class TrackBall {
public PMatrix3D rotationMat;
public Vec2D pos = new Vec2D();
private int frameCountDragging;
private Quaternion rotationQuat, tempQuat;
TrackBall() {
rotationQuat = new Quaternion();
tempQuat = new Quaternion();
rotationMat = new PMatrix3D();
}
// Specify the 2D coordinates of the object in the screen
// This could be upgraded to 3D eventually.
public void setPos(float x, float y) {
pos.set(x, y);
}
// Call this method when dragging the mouse
// with previous and current mouse position
public void drag(int x0, int y0, int x1, int y1) {
Vec3D v2 = project(x0 - pos.x, y0 - pos.y);
Vec3D v1 = project(x1 - pos.x, y1 - pos.y);
float dt = constrain(v1.distanceTo(v2) / width, -1, 1);
Vec3D n = v2.cross(v1).normalizeTo(dt);
if (frameCountDragging++ == 50) {
frameCountDragging = 0;
rotationQuat.normalize();
}
rotationQuat = tempQuat
.set(cos(asin(dt)), n)
.multiply(rotationQuat);
rotationMat.set(rotationQuat.toMatrix4x4().toFloatArray(null));
}
private Vec3D project (float x, float y) {
float radius = width / 2;
Vec3D v = new Vec3D(x, y, 0);
float len = v.magnitude();
float tr = radius / sqrt(2);
v.z = len < tr ? sqrt(radius * radius - len * len) : tr * tr / len;
return v;
}
}
I’m sure it can be improved. I leave that as an exercise for the reader