//======================================================== // Transforms - a simple applet demonstrating transformations // Written by Chris DeCoro - cdecoro@cat.nyu.edu // Sept. 9, 2002 //======================================================== import java.util.*; import java.net.*; import java.io.*; import java.awt.*; import java.awt.event.*; import Geometry.*; class Vertex { Vector3 pos=null; Vector3 relPos=null; Vector3 nowPos=null; Vector3 normal=null; Color color=null; double[] texCoord = new double[2]; int bone=-1; } class Triangle { int texID; Vertex vtx[] = new Vertex[3]; } class Texture { int id; String name; } class Bitmap { int fileSize; int width; int height; int bitsPerPixel; int compression; int[] colorTable=null; int[] indices=null; int[] colors=null; } public class Kinematics extends ThreeDimensionalApplet { //vertices[] and faces[] contain the currently loaded geometry private Vector3[] vertices; //private Face[] faces; //Given a URL, opens an Internet connection and returns a stream InputStream openModel(String fileURL) { try { URL url = new URL(fileURL); return url.openStream(); } catch(Exception e) { System.out.println(e); throw new RuntimeException("Error in reading file"); } } double degree(double radians) { return radians * 180 / Math.PI; } //function readShortLE //will read a short value in little endian format, and will then //convert into Java bigendian format int readShortLE(DataInput in) throws IOException { byte a = in.readByte(); //least significant byte b = in.readByte(); //most significant return (b << 8) | a; } int readIntLE(DataInput in) throws IOException { byte a = in.readByte(); //least significant byte b = in.readByte(); byte c = in.readByte(); byte d = in.readByte(); //most significant //return (d << 24) &255 | (c << 16) &255 | (b << 8) &255 | a &255; return ((d&255) << 24) | ((c&255) << 16) | ((b&255) << 8) | a &255; } Bitmap readTexture(InputStream stream) { try { DataInputStream in = new DataInputStream(stream); Bitmap bmp = new Bitmap(); int datum; //Read the file signature (size 2, off 0); datum = readShortLE(in); if( datum != 19778 ) //BM throw new RuntimeException("Bitmpa file signature not found"); //Read the file size; (size 4, off 2) readIntLE(in); //Read both reserved fields (size 4, off 6) readShortLE(in); readShortLE(in); //Read data offset (size 4, off 10) readIntLE(in); //Read INFOHEADER size (size 4, off 14, start of BITMAPINFO header) //Color table will be found at 14 + headerSize; int headerSize = readIntLE(in); //Read width, height (size 8, off 18) bmp.width = readIntLE(in); bmp.height = readIntLE(in); //Read image planes (size 2, off 26) readShortLE(in); //Read bits per pixel (size 2, off 28) bmp.bitsPerPixel = readShortLE(in); if( bmp.bitsPerPixel == 8 ) bmp.colorTable = new int[256]; else if( bmp.bitsPerPixel == 24 ) bmp.colorTable = null; else throw new RuntimeException("Unknown number of colors in pallete"); //Read image compression, image size (size 8, off 30) readIntLE(in); readIntLE(in); //Read 4 junk fields (size 16, off 38) readIntLE(in); readIntLE(in); readIntLE(in); readIntLE(in); //We are now at offset 54 , read over the rest of the header for(int i=0; i<(14+headerSize)-54; i++) in.readByte(); //Finally, we have the image data itself //We do different things for palleted vs. full color int dataSize = bmp.width*bmp.height; bmp.colors = new int[dataSize]; if( bmp.bitsPerPixel == 8 ) { //Next is the color table - only read if 8bpp, thus 256 in size for(int i=0; i<256; i++) { byte red, green, blue; blue = in.readByte(); green = in.readByte(); red = in.readByte(); in.readByte(); bmp.colorTable[i] = pack(red, green, blue); } bmp.indices = new int[dataSize]; bmp.colors = new int[dataSize]; for(int i=0; i maxAbs ) maxAbs = Math.abs(teapot_data[i][j]); for(int i=0; i 0); rightButton = ((e.modifiers & e.META_MASK) > 0); leftButton = !centerButton && !rightButton; if( !modified ) { downX = x; downY = y; oldTranslateX = translateX; oldTranslateY = translateY; oldScaleX = scaleX; oldScaleY = scaleY; oldTheta = theta; //I dont think we use this anymore oldRotationMatrix = rotationMatrix; } else { for(int k=0; k 0); rightButton = ((e.modifiers & e.META_MASK) > 0); leftButton = !centerButton && !rightButton; shiftDown = ((e.modifiers & e.SHIFT_MASK) > 0); double hw=bounds().width/2, hh=bounds().height/2; if( modified && selectedK != -1 ) { /* Vector3 pt = new Vector3(geometryMatrices[selectedK][0].get(selectedI,selectedJ), geometryMatrices[selectedK][1].get(selectedI,selectedJ), geometryMatrices[selectedK][2].get(selectedI,selectedJ)); */ Vector3 pt = new Vector3(teapot_data[selectedK][3*selectedI+0], teapot_data[selectedK][3*selectedI+1], teapot_data[selectedK][3*selectedI+2]); Vector3 proj = project(pt); Vector3 mousePos = new Vector3(x, y, proj.z()); Vector3 newPt = unProject(mousePos); teapot_data[selectedK][selectedI*3+0] = newPt.x(); teapot_data[selectedK][selectedI*3+1] = newPt.y(); teapot_data[selectedK][selectedI*3+2] = newPt.z(); /* geometryMatrices[selectedK][0].set(selectedI,selectedJ,newPt.x()); geometryMatrices[selectedK][1].set(selectedI,selectedJ,newPt.y()); geometryMatrices[selectedK][2].set(selectedI,selectedJ,newPt.z()); */ makeAllPatches(); return true; } //Virtual trackball rotation if(leftButton && !shiftDown) { double radius=hw; //Determine the center of the object in screen coords //This will be the center of our sphere Vector3 center = new Vector3(translateX+hw,translateY+hh,0); Vector3 i1, i2; //Project a line from the mouse click locations through the sphere //i1 is the initial mouse down position, i2 is the current position //We could optimize by computing i1 once in mouseDown, but oh well i1 =isectLineSphere( new Vector3(downX,downY,radius), new Vector3(downX,downY,0), center, radius); i2 =isectLineSphere( new Vector3(x,y,radius), new Vector3(x,y,0), center, radius); //If either are null (no collision) just return if( i1 != null && i2 != null ) { //Determine the vectors from the center of the sphere // to the point on the surface //We can optimize by removing the normalize() with div(radius) Vector3 v1, v2; v1 = i1.sub(center).normalize(); v2 = i2.sub(center).normalize(); //Determine the axis of rotation and the angle Vector3 a = v1.cross(v2); double theta = Math.asin(a.norm()); //We have problems when the angle is > 90 //The following code computes the angle correctly, but will dont rotate right //So just stop rotating when we move too far away if(v1.dot(v2) < 0) { theta = Math.PI - theta; //gets right angle, but still problems //theta = theta + Math.PI/2; //this is wrong setDamage(); return true; } //Make some practical adjustments to account for the flipped y axis rotationAxis = new Vector3(a.x(), -a.y(), a.z()).normalize(); //TODO: Find out if we should comment this next line //rotationAxis = (new Matrix4x4()).rotateZ(theta).mul(rotationAxis); //Convert to degrees rotationTheta = theta*180/Math.PI; //System.out.println("Dot: " + v1.dot(v2) + " angle: " + rotationTheta); //Update the rotation matrix //rotationMatrix = (oldRotationMatrix).rotate(rotationAxis, rotationTheta); rotationMatrix = (new Matrix4x4()).rotate(rotationAxis, rotationTheta).mul(oldRotationMatrix); } } else if(leftButton && shiftDown) { //We have the vector from the center to the mouse-down position //We can compute the vector to the Vector3 center = new Vector3(translateX+hw,translateY+hh,0); Vector3 handle = (new Vector3(x, y, 0)).sub(center); Vector3 start = (new Vector3(downX,downY,0)).sub(center); //Get the angle from the dot product, dividing by norms and takign arccos //If we wanted to be clever, we could keep the cos as it is, find sin // using sin = sqrt(1-cos^2) and plug sin,cos into the rotatematrix directly //This is a 2d operation double dot = handle.dot(start); double angle = Math.acos(dot/handle.norm()/start.norm())*180/Math.PI; //If we went left (i think its left), take the negative angle //We will know if the crossproduct is pointing back at us if( handle.cross(start).z() > 0 ) angle = -angle; //Update the old rotation matrix //rotationMatrix = (oldRotationMatrix).rotateZ(angle); rotationMatrix = (new Matrix4x4()).rotateZ(angle).mul(oldRotationMatrix); } else if(centerButton) { //Translate the object based on mouse pos. translateX = oldTranslateX + (x - downX); translateY = oldTranslateY + (y - downY); } else if(rightButton) { //Rescale the object based on the mouse position scaleX = oldScaleX + (x - downX)/50.0; scaleY = oldScaleY + -(y - downY)/50.0; } setDamage(); return true; } //Just reset the mouse positions to their old values public boolean mouseUp(Event e, int x, int y) { boolean leftButton, centerButton, rightButton; centerButton = ((e.modifiers & e.ALT_MASK) > 0); rightButton = ((e.modifiers & e.META_MASK) > 0); leftButton = !centerButton && !rightButton; if( downX == -1 ) return true; selectedK = -1; selectedI = -1; selectedJ = -1; downX = -1; downY = -1; setDamage(); return true; } boolean cullFace=true; boolean drawAxis=true; boolean wire=false; boolean doLighting=true; int selectedMode = 0; int theAnimation = 1; boolean skeletonOnly = false; boolean drawControl = true; static final int ANIMATION_COUNT = 7; public boolean keyUp(Event evt, int key) { switch(key) { case Event.TAB: modified = false; selectedK = -1; selectedJ = -1; selectedI = -1; break; } return true; } //Toggle a couple of settings, based on input public boolean keyDown(Event evt, int key) { switch(key) { case 'b': cullFace = !cullFace; break; case 'x': drawAxis = !drawAxis; break; case 'w': wire = !wire; wireframe(wire); break; case 's': skeletonOnly = !skeletonOnly; break; case 'l': doLighting = !doLighting; break; case ' ': theAnimation = (theAnimation+1) % ANIMATION_COUNT; break; case Event.ESCAPE: translateX = translateY = oldTranslateX = oldTranslateY = theta = oldTheta = rotationTheta = 0; scaleX = scaleY = oldScaleX = oldScaleY = 1; rotationMatrix = oldRotationMatrix = new Matrix4x4(); break; case '-': SEGMENTS--; if( SEGMENTS < 1 ) SEGMENTS = 1; makeAllPatches(); break; case '+': case '=': SEGMENTS++; makeAllPatches(); break; case 'c': drawControl = !drawControl; break; case Event.TAB: modified = true; break; } setDamage(); return true; } //Settings for transformations int downX=-1, downY=-1; double translateX=0, translateY=0; double oldTranslateX=0, oldTranslateY=0; //double scaleX=1, scaleY=1; //double scaleX=0.32, scaleY=0.32; double scaleX=0.2, scaleY=0.2; double oldScaleX=1, oldScaleY=1; double theta=0, oldTheta=0; Vector3 rotationAxis=new Vector3(1, 0, 0); double rotationTheta=0; //Matrix4x4 rotationMatrix=new Matrix4x4(); Matrix4x4 rotationMatrix=new Matrix4x4(0.208, .997, -.009, 0, .0026, -.0101, -.999, 0, -.978, .208, -.0046, 0, 0, 0, 0, 1); Matrix4x4 oldRotationMatrix=new Matrix4x4(); public static void main(String[] args) { Frame frame = new Frame("Kinematics"); Kinematics pnl = new Kinematics(); pnl.setSize(900, 900); frame.setSize(900, 900); frame.add(pnl); frame.show(); frame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { e.getWindow().dispose(); System.exit(0); } } ); pnl.init(); pnl.start(); } }