diff --git a/ArcBall.pde b/ArcBall.pde new file mode 100644 index 0000000..fef18e4 --- /dev/null +++ b/ArcBall.pde @@ -0,0 +1,155 @@ + +import draw3D.geo.Quaternion; +import processing.core.*; + +/** + * ArcBall is an interface for rotate 3D objects. It allows the rotation of a 3D + * object from a 2D input by simulating touching a sphere (arcball) that can be + * spun in any direction. + * + * @author Kelly Egan + * + */ +public class ArcBall implements PConstants{ + + /** + * Constants for calling specific views when using setViews + */ + public final Quaternion FRONT = new Quaternion(1.0f, 0.0f, 0.0f, 0.0f); + public final Quaternion TOP = new Quaternion(-PApplet.sqrt(2.0f)/2.0f, PApplet.sqrt(2.0f)/2.0f, 0, 0); + public final Quaternion LEFT = new Quaternion(PApplet.sqrt(2.0f)/2.0f, 0, PApplet.sqrt(2.0f)/2.0f, 0); + public final Quaternion RIGHT = new Quaternion(-PApplet.sqrt(2.0f) / 2.0f, 0f, PApplet.sqrt(2.0f) / 2.0f, 0f); + public final Quaternion BOTTOM = new Quaternion(PApplet.sqrt(2.0f)/2.0f, PApplet.sqrt(2.0f)/2.0f, 0, 0); + public final Quaternion BACK = new Quaternion(0.0f, 0.0f, 1.0f, 0.0f); + + private PApplet p; + + //Center position and radius of ArcBall + private PVector center; + private float radius; + + //Start and end point on ArcBall surface of rotation + private PVector startPoint, endPoint; + + private boolean dragging; + + public Quaternion currentRotation, startRotation, deltaRotation; + public Quaternion quaternion; + + /** + * Construct an ArcBall given its center position and radius + * + * @param p parent PApplet + * @param center_x x coordinate of the center of ArcBall + * @param center_y y coordinate of the center of the ArcBall + * @param radius radius of the ArcBall + */ + public ArcBall(PApplet p, float center_x, float center_y, float radius){ + this.p = p; + + center = new PVector(center_x, center_y); + + this.radius = radius; + + startPoint = new PVector(); + endPoint = new PVector(); + + quaternion = new Quaternion(); + currentRotation = new Quaternion(); + startRotation = new Quaternion(); + deltaRotation = new Quaternion(); + + + } + + /** + * Called when dragging of the ArcBall begins. + * @param x screen position on x axis + * @param y screen position on y axis + */ + public void dragStart(float x, float y){ + startPoint = screenToArcball(x, y); + startRotation.set(currentRotation); + deltaRotation.setToIdentity(); + dragging = true; + } + + /** + * Called during dragging of ArcBall + * @param x + * @param y + */ + public void dragging(float x, float y){ + endPoint = screenToArcball(x, y); + quaternion.rotationBetween(startPoint, endPoint, deltaRotation); + } + + /** + * Called when draggin ends + */ + public void dragEnd() { + dragging = false; + } + + + + /** + * Update currentRotation with data from dragging + */ + public void update(){ +// if( dragging ) { + currentRotation = quaternion.mult(deltaRotation, startRotation); +// } else { +// Quaternion.mult(deltaRotation, currentRotation, currentRotation); +// deltaRotation = deltaRotation.power(0.8f); +// } + applyRotation(currentRotation); + } + + /** + * Project screen coordinates onto ArcBall sphere. If coordinate + * is outside of the sphere treat as edge of sphere. + * + * @param x + * @param y + * @return + */ + public PVector screenToArcball(float x, float y){ + PVector result = new PVector(); + result.x = (x - center.x) / radius; + result.y = (y - center.y) / radius; + + float mag = result.magSq(); + + if (mag > 1.0f){ + result.normalize(); + } else { + result.z = PApplet.sqrt(1.0f - mag); + } + + return result; + } + + /** + * Set the view to one of six stand views + * + * @param viewIndex + */ + public void setView( Quaternion view ) { + startRotation.set(view); + deltaRotation.setToIdentity(); + } + + public void applyRotation(Quaternion q){ + float[] axisAngle = q.toAngleAxis(); + p.rotate(axisAngle[0], axisAngle[1], axisAngle[2], axisAngle[3]); + } + + public float[] getRotation() { + return currentRotation.toAngleAxis(); + } + + public float[] getInverseRotation() { + return currentRotation.inverse().toAngleAxis(); + } +} diff --git a/Quaternion.pde b/Quaternion.pde new file mode 100644 index 0000000..4eb6149 --- /dev/null +++ b/Quaternion.pde @@ -0,0 +1,149 @@ + +import processing.core.*; + +public class Quaternion implements PConstants { + public float w, x, y, z; + + /** + * Contructs a new Quaternion and it set to the identity + */ + public Quaternion() { + setToIdentity(); + } + + public Quaternion(float w, float x, float y, float z) { + set(w, x, y, z); + } + + /** + * Set the value of Quaternions components. + * + * @param w scalar component + * @param x vector components x value + * @param y vector components y value + * @param z vector components z value + */ + public void set(float w, float x, float y, float z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Set the value of a Quaternions components with the values of another Quaternion + * @param q + */ + public void set(Quaternion q) { + this.set(q.w, q.x, q.y, q.z); + } + + public void set(float s, PVector v) { + set(s, v.x, v.y, v.z); + } + + /** + * Sets Quaternion to the identity Quaternion + */ + public void setToIdentity() { + set( 1.0f, 0.0f, 0.0f, 0.0f ); + } + + public void mult(Quaternion q1, Quaternion q2, Quaternion target) { + float s = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; + float vx = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; + float vy = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z; + float vz = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x; + target.set(s, vx, vy, vz); + } + + public Quaternion mult(Quaternion q1, Quaternion q2) { + Quaternion result = new Quaternion(); + this.mult(q1, q2, result); + return result; + } + + public Quaternion mult(float s) { + return new Quaternion(w*s, x*s, y*s, z*s); + } + + public void inverse(Quaternion source, Quaternion target) { + target.set( source.w, -source.x, -source.y, -source.z ); + } + + public Quaternion inverse() { + return new Quaternion(this.w, -this.x, -this.y, -this.z); + } + + /** + * Computes the power of the quaternion, defined as follows: + * let Q=(cos(alpha), sin(alpha) u), + * then Q^t = (cos(t*alpha), sin(t*alpha)u). + */ + public Quaternion power(float exponent) { + float theta2 = PApplet.acos(w); + theta2 *= exponent; + PVector U = new PVector(x,y,z); + U.normalize(); + U.mult(PApplet.sin(theta2)); + return new Quaternion(PApplet.cos(theta2),U.x, U.y, U.z); + } + + public String toString() { + return "(" + w + ", " + x + ", " + y + ", " + z + ")"; + } + + /** + * Set target Quaternion to the rotation between two unit vectors (shortest arc) + * + * @param start normalized starting point on rotation + * @param end normalized ending point of rotation + */ + public void rotationBetween( PVector start, PVector end, Quaternion target ) { + float dot = PVector.dot(start, end); + + if( dot < -0.999999) { + //Start and end vectors are opposites rotation should be 180 degrees + PVector xAxis = new PVector(1, 0, 0); + PVector cross = xAxis.cross(start); + //If cross product is zero rotate around Y instead of x + if( cross.mag() > 0.000001) { + target.set(0.0f, 1.0f, 0.0f, 0.0f); //180 degree rotation around the x-axis + } else { + target.set(0.0f, 0.0f, 1.0f, 0.0f); //180 degree rotation around y-axis + } + + } else if ( dot > 0.999999) { + //Start and end vectors are the same rotation should do nothing + target.setToIdentity(); + } else { + //Start and end vectors are not identical or opposite + target.set( dot, start.cross(end) ); + } + } + + public void fromAngleAxis( float angle, PVector axis, Quaternion target) { + float s = PApplet.sin(angle/2); + target.set(PApplet.cos(angle/2), axis.x * s, axis.y * s, axis.z * s); + } + + /** + * Convert Quaternion to an angle and Axis pair + * @return float array whos first value is angle and remaining values represent the axis + */ + public float[] toAngleAxis() { + float[] result = new float[4]; + + float sa = (float) Math.sqrt(1.0f - w * w); + if (sa < EPSILON) { + sa = 1.0f; + } + + result[0] = (float) Math.acos(w) * 2.0f; + result[1] = x / sa; + result[2] = y / sa; + result[3] = z / sa; + + return result; + } +} diff --git a/sketch3d.pde b/sketch3d.pde index 87d0f12..d14944c 100644 --- a/sketch3d.pde +++ b/sketch3d.pde @@ -11,6 +11,8 @@ import java.awt.Color; import processing.dxf.*; import processing.pdf.*; + + boolean drawingNow, moveDrawing, rotatingNow, pickingColor, changingPreferences, pickingBackground; //Current button states boolean up, down, left, right; @@ -22,6 +24,9 @@ Skeleton skeleton; String kinectStatus, keyStatus; int keyCount = 0; +//Controller +ArcBall arcBall; + //Drawing Drawing d; Brush defaultBrush; @@ -68,7 +73,7 @@ PMatrix3D inverseTransform; PVector offset, rotation; PVector moveStart, moveNow, moveDelta, moveModel, oldOffset; -PVector drawingHand, drawingHandTransformed, secondaryHand, secondaryHandTransformed; +PVector drawingHand, drawingHandTransformed, secondaryHand, secondaryHandTransformed, drawingHandScreen, secondaryHandScreen; PVector rotationStarted, rotationEnded, oldRotation, rotationCenter; PVector startPosition, currentPosition, positionDelta; PShader fogShader, shader; @@ -122,6 +127,9 @@ void setup() { //Drawing d = new Drawing(this, "default.gml"); brushSize = 30.0; + + //Controller + arcBall = new ArcBall(this, width/2, height/2, 300); brushColorHSB = new PVector(0.0, 0.0, 1.0); oldBrushColorHSB = new PVector(); @@ -169,6 +177,8 @@ void setup() { shader.set("zPlaneIndicatorOn", true); drawingHand = new PVector(); + drawingHandScreen = new PVector(); + secondaryHandScreen = new PVector(); drawingHandTransformed = new PVector(); secondaryHand = new PVector(); secondaryHandTransformed = new PVector(); @@ -230,9 +240,12 @@ void draw() { } camera( cameraPos.x, cameraPos.y, cameraPos.z, cameraFocus.x, cameraFocus.y, cameraFocus.z, 0, 1, 0); + drawingHandScreen.set( width - screenX( drawingHand.x, drawingHand.y, drawingHand.z), height - screenY( drawingHand.x, drawingHand.y, drawingHand.z) ); + secondaryHandScreen.set( width - screenX( secondaryHand.x, secondaryHand.y, secondaryHand.z), height - screenY( secondaryHand.x, secondaryHand.y, secondaryHand.z) ); + //Set the cursor for the menus if( menuState != FILE_MENU ) { - cp5.getPointer().set( width-(int)screenX( drawingHand.x, drawingHand.y, drawingHand.z), height-(int)screenY( drawingHand.x, drawingHand.y, drawingHand.z) ); + cp5.getPointer().set( (int)drawingHandScreen.x, (int)drawingHandScreen.y ); } else { cp5.getPointer().set( mouseX, mouseY ); } @@ -252,8 +265,8 @@ void draw() { scale( 0.001, -0.001, 0.001 ); } - rotateX(rotation.x); - rotateY(rotation.y); + //ROTATION + arcBall.update( ); if ( displayOrigin && !exportDXF && !exportPDF) { strokeWeight(3); @@ -318,18 +331,18 @@ void update() { d.clearStrokes(); } - if ( up ) { - rotation.x += rotationStep; - } - if ( down ) { - rotation.x -= rotationStep; - } - if ( right ) { - rotation.y += rotationStep; - } - if ( left ) { - rotation.y -= rotationStep; - } +// if ( up ) { +// rotation.x += rotationStep; +// } +// if ( down ) { +// rotation.x -= rotationStep; +// } +// if ( right ) { +// rotation.y += rotationStep; +// } +// if ( left ) { +// rotation.y -= rotationStep; +// } if (deviceReady) { kinect.update(); @@ -346,10 +359,8 @@ void update() { d.addPoint( (float)millis() / 1000.0, drawingHandTransformed.x, drawingHandTransformed.y, drawingHandTransformed.z); } if ( rotatingNow ) { - rotationEnded.set(secondaryHand); - stroke(255, 0, 0); - rotation.x = oldRotation.x + map( rotationStarted.y - rotationEnded.y, -1000, 1000, -PI/2, PI/2 ); - rotation.y = oldRotation.y + map( rotationStarted.x - rotationEnded.x, -1000, 1000, -PI/2, PI/2 ); + //arcBall.dragging( mouseX, mouseY ); + arcBall.dragging(secondaryHandScreen.x, secondaryHandScreen.y ); } if ( moveDrawing && !drawingNow ) { moveNow.set( secondaryHand ); @@ -372,8 +383,8 @@ void mousePressed() { keyStatus += " Left mouse."; } if (mouseButton==RIGHT) { - rotationStarted.set(secondaryHand); - oldRotation.set( rotation ); + //arcBall.dragStart(mouseX, mouseY); + arcBall.dragStart(secondaryHandScreen.x, secondaryHandScreen.y); rotatingNow=true; keyStatus += " Right mouse."; } @@ -386,6 +397,10 @@ void mousePressed() { } } +void mouseDragged() { + arcBall.dragging(mouseX, mouseY); +} + void mouseReleased() { if ( menuState != MENUS_OFF ) { cp5.getPointer().released(); @@ -395,8 +410,10 @@ void mouseReleased() { drawingNow=false; d.endStroke(); } - if (mouseButton==RIGHT) + if (mouseButton==RIGHT) { rotatingNow=false; + arcBall.dragEnd(); + } if (mouseButton==CENTER) moveDrawing=false; } @@ -492,15 +509,9 @@ void keyPressed() { break; case 'r': case 'R': - rotationStarted.set(secondaryHand); - oldRotation.set( rotation ); - rotatingNow=true; + arcBall.dragStart(secondaryHandScreen.x, secondaryHandScreen.y); + rotatingNow=true; break; - case 't': - case 'T': - //Top view - rotation.set(-TAU / 4, 0, 0); - break; case 'q': case 'Q': exit(); @@ -513,6 +524,25 @@ void keyPressed() { case 'Z': d.undoLastStroke(); break; + case '1': + arcBall.setView( arcBall.FRONT ); + break; + case '2': + break; + case '3': + arcBall.setView( arcBall.RIGHT ); + break; + case '4': + break; + case '5': + break; + case '6': + break; + case '7': + arcBall.setView( arcBall.TOP ); + break; + case '8': + break; case '-': case '_': brushSize -= 5; @@ -587,10 +617,10 @@ void keyReleased() { } } } - -boolean sketchFullScreen() { - return true; -} +// +//boolean sketchFullScreen() { +// return true; +//} void stop() { } @@ -643,14 +673,25 @@ void loadBackground( File f ) { void updateDrawingHand() { //drawingHand.set( mouseX, mouseY, 0 ); + // + drawingHandTransformed.set( drawingHand ); secondaryHandTransformed.set( secondaryHand ); inverseTransform.reset(); if ( !moveDrawing ) { inverseTransform.translate( -offset.x, -offset.y, -offset.z ); } - inverseTransform.rotateY( PI - rotation.y ); - inverseTransform.rotateX( PI + rotation.x ); + + + float[] inverseRotation = arcBall.getInverseRotation(); + inverseTransform.rotate( inverseRotation[0], inverseRotation[1], inverseRotation[2], inverseRotation[3] ); + + inverseTransform.rotateY( PI ); + inverseTransform.rotateX( PI ); + +// //Pre-arcball rotation +// inverseTransform.rotateY( PI - rotation.y ); +// inverseTransform.rotateX( PI + rotation.x ); inverseTransform.mult( drawingHand, drawingHandTransformed ); inverseTransform.mult( secondaryHand, secondaryHandTransformed );