//======================================================== // 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 BoneFrame { double pos[] = new double[3]; double rot[] = new double[3]; } class Bone { public Bone() { for(int i=0; i= Model.MAX_TRIS) break; model.tris[tri] = new Triangle(); for(int i=0; i<3; i++) { Vertex vtx = model.tris[tri].vtx[i] = new Vertex(); /* Vertex vtx=null; try{ vtx = model.tris[tri].vtx[i]; } catch (NullPointerException e) { System.out.println("tri = " + tri); System.out.println("vtx = " + vtx); throw e; } */ vtx.bone = (int)tok.nval; tok.nextToken(); //read the position array[0] = tok.nval; tok.nextToken(); array[1] = tok.nval; tok.nextToken(); array[2] = tok.nval; tok.nextToken(); vtx.pos = new Vector3(array); //System.out.println(array[0] + " " + array[1] + " " + array[2]); // //read the normal array[0] = tok.nval; tok.nextToken(); array[1] = tok.nval; tok.nextToken(); array[2] = tok.nval; tok.nextToken(); vtx.normal = new Vector3(array); //Using the normal, compute the color at the vertex normal(vtx.normal); vtx.color = computeLightedColor(); //munch the texcoord tok.nextToken(); tok.nextToken(); } tri++; } System.out.println("Tris = " + tri); //Run the pretraverse to compute relative positions pretraverse(model.bones[0], model, new KM(), 0); /* System.out.println("Tri 1 pos:"); System.out.println(model.tris[0].vtx[0].nowPos); System.out.println(model.tris[0].vtx[1].nowPos); System.out.println(model.tris[0].vtx[2].nowPos); */ /* //Find the maximum and minimum values //max[] and min[] are the corners of the axis-aligned bbox double max[] = new double[3]; double min[] = new double[3]; max[0] = max[1] = max[2] = Double.MIN_VALUE; min[0] = min[1] = min[2] = Double.MAX_VALUE; for(int i=0; i max[j] ) max[j] = vertices[i].array()[j]; } } //Find the center of the object double center[] = new double[3]; for(int i=0; i<3; i++) center[i] = (max[i] + min[i])/2; //Find the scale factor double width[] = new double[3]; for(int i=0; i<3; i++) width[i] = max[i] - min[i]; double scale; if(width[0] > width[1]) scale = width[0]; else scale = width[1]; if(width[2] > scale) scale = width[2]; //Recenter and rescale Vector3 vCenter = new Vector3(center[0], center[1], center[2]); for(int i=0; i"); System.out.println( bone.id ); */ //Transform according to this node's settings //try { bone.position = km.transform( new double[]{frame.pos[0],frame.pos[1],frame.pos[2]} ); /* } catch (NullPointerException e) { System.out.println("Frame #: " + frame); System.out.println("Anim #: " + animation); System.out.println("Bone #: " + bone.id); System.out.println("Frame: " + frame); throw e; } */ km.translate( frame.pos[0], frame.pos[1], frame.pos[2] ); km.rotateZ( degree(frame.rot[2]) ); km.rotateY( degree(frame.rot[1]) ); //pretty good with pos, neg, pos km.rotateX( degree(frame.rot[0]) ); color3i(255, 0, 0); begin(RENDER_POINTS); vertex( bone.position ); end(); switch(bone.id) { case 2: case 3: case 4: case 14: case 15: case 16: case 17: color3i(255, 0, 0); break; case 5: case 6: case 7: case 18: case 19: case 20: case 21: color3i(0, 0, 255); break; default: color3i(0, 255, 0); break; } //color3i(0, 255, 0); begin(RENDER_LINES); if( bone.id != 0 ) { vertex( bone.parent.position ); vertex( bone.position ); } end(); //compute current pos for each vertex using bone.position and vert.relpos // to do this, we transform with the km //We cannot render yet, because we must transform all points of tri before render // the points may be connected to different bones for(int i=0; i 0); rightButton = ((e.modifiers & e.META_MASK) > 0); leftButton = !centerButton && !rightButton; downX = x; downY = y; oldTranslateX = translateX; oldTranslateY = translateY; oldScaleX = scaleX; oldScaleY = scaleY; oldTheta = theta; //I dont think we use this anymore oldRotationMatrix = rotationMatrix; setDamage(); return true; } //Here is where we actually perform the transformations public boolean mouseDrag(Event e, int x, int y) { //See what buttons are down //This is a real mess, but I think its JDK1.0 compilant boolean leftButton, centerButton, rightButton, shiftDown; centerButton = ((e.modifiers & e.ALT_MASK) > 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; //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; downX = -1; downY = -1; setDamage(); return true; } boolean cullFace=true; boolean drawAxis=true; boolean wire=true; int selectedMode = 0; int theAnimation = 1; boolean skeletonOnly = false; static final int ANIMATION_COUNT = 7; //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; break; case 's': skeletonOnly = !skeletonOnly; 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; } 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 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(600, 600); frame.setSize(600, 600); frame.add(pnl); frame.show(); pnl.start(); } } class ThreeDimensionalApplet extends GenericApplet { int x = 100, y = 100; //function setContext //Sets the current graphics object to draw on void setContext(Graphics g) { graphics = g; } //function begin //Starts a primitive-drawing operation void begin(int shapeType) { if( renderState != RENDER_NONE ) { throw new RuntimeException("Cannot call begin() twice without intervening end()"); } if( shapeType < RENDER_NONE || shapeType > RENDER_LAST ) { throw new RuntimeException("Attempted to draw an invalid primitive"); } renderState = shapeType; sentVertices = 0; polygon = new Polygon(); } //function end //Concludes a primitive-drawing operation void end() { if( renderState == RENDER_NONE ) { throw new RuntimeException("Cannot call end() without begin()"); } else if( renderState == RENDER_POLYGON ) { if(!cullPolygon || counterClockwise(polygon)) graphics.fillPolygon(polygon); } else if( renderState == RENDER_OPEN_POLYGON ) { graphics.drawPolygon(polygon); } renderState = RENDER_NONE; } //functions color3d, color3i, color //Sets the current color. //Drawing functions will use the current color when a primitive is drawn //color3d accepts values in the range [0.0, 1.0] //color3i accepts values in the range [0 255] //color accepts a java.awt.Color object //They are given seperate names to avoid accidental type promotion void color3d(double r, double g, double b) { graphics.setColor(new Color((int)(r*255.0), (int)(g*255.0), (int)(b*255.0))); } void color3i(int r, int g, int b) { graphics.setColor(new Color(r,g,b)); } void color(Color color) { graphics.setColor(color); } //function vertex //Submits vertices for drawing primitives, 2d integer form void vertex(int x, int y) { vertex((double)x, (double)y, 0); } //function vertex //Submits vertices for drawing primitives, double form void vertex(double x, double y, double z) { vertex(new Vector3(x,y,z)); } //function vertex(Vector3) //Submits vertices for drawing primitives, vector/double form void vertex(Vector3 worldCoord) { //Three different coordinates //worldCoord is the input coordinate //transCoord is the post-modelview-transformation coordinate //screenCoord is the post-projection coordinate boolean fastMul = true; int screenX, screenY; //This following section is probably the key part of the assignment // Here is where we take the vertex input and transform it, // according to the current transformation matrix // We assume we have orthogonal projection, so applying the projection matrix // would be (more or less) equivalent to dropping the z coordinate //To save time, we use a very optimized partial matrix multiplier, fastMul() // see the notes in Geometry/Matrix4x4.java on our hacks if( fastMul ) { double[] transCoord; transCoord = transformMatrix.fastMul(worldCoord); screenX = (int)transCoord[0]; screenY = (int)transCoord[1]; } else { //This way is a more proper, but slower, version above the above //It uses a general (complete) matrix multipler, as opposed to the other // which makes assumptions of the Matrix input Vector3 transCoord, screenCoord; transCoord = transformMatrix.mul(worldCoord); screenCoord = transCoord; screenX = (int)screenCoord.x(); screenY = (int)screenCoord.y(); } //Take different actions based on the render state //For most primitives, we test if we need to light before drawing if( renderState == RENDER_NONE ) { throw new RuntimeException("Must call begin() before sending a vertex"); } else if( renderState == RENDER_POINTS ) { //For points, jsut draw an appropriatly sized box on every vertex() if(lighting) graphics.setColor(computeLightedColor()); graphics.fillRect(screenX-pointSize/2, screenY-pointSize/2, pointSize, pointSize); } else if( renderState == RENDER_LINES ) { //For lines, call drawLine() for every other vertex() if( sentVertices == 0 ) { lineX = screenX; lineY = screenY; sentVertices++; } else { if(lighting) graphics.setColor(computeLightedColor()); graphics.drawLine(lineX, lineY, screenX, screenY); sentVertices = 0; } } else if( renderState == RENDER_LINE_STRIP ) { //For line strips, perform drawLine() on every vertex() after the first //A line strip is the same thing as a polyline in many draw programs if( sentVertices > 0 ) { if(lighting) graphics.setColor(computeLightedColor()); graphics.drawLine(lineX, lineY, screenX, screenY); } lineX = screenX; lineY = screenY; sentVertices++; } else if( renderState == RENDER_POLYGON || renderState == RENDER_OPEN_POLYGON || renderState == RENDER_TRIANGLE || renderState == RENDER_OPEN_TRIANGLE ) { //For all of the polygon types, we will need to store the additional vertex //With triangles, we will draw on the third vtx, below //With polygons, we draw on the end() call polygon.addPoint(screenX, screenY); sentVertices++; } //Draw a triangle on every third vertex if( renderState == RENDER_TRIANGLE && sentVertices == 3 ) { //Perform back face culling if(!cullPolygon || counterClockwise(polygon)) { if(lighting) graphics.setColor(computeLightedColor()); graphics.fillPolygon(polygon); } polygon = new Polygon(); sentVertices = 0; } else if( renderState == RENDER_OPEN_TRIANGLE && sentVertices == 3 ) { //perform back face culling if(!cullPolygon || counterClockwise(polygon)) { if(lighting) graphics.setColor(computeLightedColor()); graphics.drawPolygon(polygon); } polygon = new Polygon(); sentVertices = 0; } } //function identity void identity() { transformMatrix = new Matrix4x4(); } //function transform(Matrix) //Sets the current modelview transform matrix //Essentially equivalent to glLoadMatrix() void transform(Matrix4x4 mat) { //transformMatrix = mat.mul(transformMatrix); transformMatrix = transformMatrix.mul(mat); //System.out.println(mat); //We want to append this matrix onto the end of the chain //Therefore out = in * mat } //function getTransform() //Retrieves the current transform matrix Matrix4x4 getTransform() { return transformMatrix; } //function pushMatrix //Pushes the current transform matrix onto the transform stack //Essentially equivalent to glPushMatrix() void pushMatrix() { transformStack.push( transformMatrix ); } //function popMatrix //Pushes the current transform matrix onto the transform stack //Essentially equivalent to glPopMatrix() void popMatrix() { transformMatrix = (Matrix4x4)transformStack.peek(); transformStack.pop(); } //function project //Given the current modelview and projection matrices, //Projects the input in world coordinates to screen coordinates //Similar to gluProject() Vector3 project(Vector3 world) { return transformMatrix.mul(world); } //function unProject //Given the current modelview and projection matrices, //Performs the inverse of the projection operation, which switches from //screen coordinates to world coordinates; Similar to gluUnProject() Vector3 unProject(Vector3 screen) { return transformMatrix.inverse().mul(screen); } //function setDamage //Informs the system that the window has been damaged/invalidated //and needs to be redrawn void setDamage() { damage = true; } //Render modes - passed to begin() static final int RENDER_NONE=0; static final int RENDER_LINES=1; static final int RENDER_LINE_STRIP=2; static final int RENDER_POLYGON=3; static final int RENDER_OPEN_POLYGON=4; static final int RENDER_TRIANGLE=5; static final int RENDER_OPEN_TRIANGLE=6; static final int RENDER_POINTS=7; static final int RENDER_LAST=RENDER_POINTS; //Option settings - passed to enable() / disable() static final int RENDER_CULL_FACE=256; static final int RENDER_LIGHTING=257; void enable(int param) { switch(param) { case RENDER_CULL_FACE: cullPolygon = true; break; case RENDER_LIGHTING: lighting = true; break; default: throw new RuntimeException("Invalid parameter passed to enable"); } } void disable(int param) { switch(param) { case RENDER_CULL_FACE: cullPolygon = false; break; case RENDER_LIGHTING: lighting = false; break; default: throw new RuntimeException("Invalid parameter passed to enable"); } } //function pointSize //Sets the size of the boxes used to draw the point primitives void pointSize(int size) { pointSize = size; } //function normal //Sets the current normal, used in dynamic lighting calculations void normal(Vector3 vec) { currentNormal = vec; } //function lightDirection //Sets the direction of the incoming light (we have only one) //Note that we assume light is at infinite distance void lightDirection(Vector3 pos) { lightPos = pos; } //Various functions to set lighting properties //These include ambient/diffuse color/material/weight-coefficient //As well as functions to set ambient & diffuse at same time void ambientColor(int r, int g, int b) { ambientColor = new Color(r, b, g); } void ambientMaterial(int r, int g, int b) { ambientMaterial = new Color(r, b, g); } void ambientAndDiffuseMaterial(int r, int g, int b) { ambientMaterial = new Color(r, b, g); diffuseMaterial = new Color(r, g, b); } void ambientAndDiffuseColor(int r, int g, int b) { ambientColor = new Color(r, b, g); diffuseColor = new Color(r, g, b); } void diffuseColor(int r, int g, int b) { diffuseColor = new Color(r, g, b); } void diffuseMaterial(int r, int g, int b) { diffuseMaterial = new Color(r, g, b); } void ambientCoeff(double coeff) { ambientCoeff = coeff; } void diffuseCoeff(double coeff) { diffuseCoeff = coeff; } //function computeLightedColor //Given the current light settings and normal, //computes the Phong difuse lighting equation and returns the color public Color computeLightedColor() { double rgb[] = new double[3]; for(int i=0; i<3; i++) { rgb[i] = ambientCoeff*getRGB(ambientColor,i)*getRGB(ambientMaterial,i) + diffuseCoeff*getRGB(diffuseColor,i)*getRGB(diffuseMaterial,i) * max(0,currentNormal.dot(lightPos)); } return new Color(d2i(rgb[0]), d2i(rgb[1]), d2i(rgb[2])); } //function isectLineSphere //Determines the intersection of a line (not segment) from p1->p2 // with a sphere centered at p3 w/ radius r. //Returns the closest intersection to the start of the ray Vector3 isectLineSphere(Vector3 p1, Vector3 p2, Vector3 p3, double r) { //Extract fields to make them easier to deal with double x1, x2, x3; double y1, y2, y3; double z1, z2, z3; x1 = p1.x(); x2 = p2.x(); x3 = p3.x(); y1 = p1.y(); y2 = p2.y(); y3 = p3.y(); z1 = p1.z(); z2 = p2.z(); z3 = p3.z(); //Substitue the equations of the line "p1 + u*(p2-p1)" //Into the equation of the circle "(x-p3.x)^2 + (y-p3.y)^2 + (z-p3.z)^2 = r" //And we get a quadratic form a*u^2 + b*u + c, where a,b,c are the following double a, b, c; a = sq(x2-x1) + sq(y2-y1) + sq(z2-z1); b = 2*( (x2-x1)*(x1-x3) + (y2-y1)*(y1-y3) + (z2-z1)*(z1-z3) ); c = sq(x3) + sq(y3) + sq(z3) + sq(x1) + sq(y1) + sq(z1) - 2*(x3*x1 + y3*y1 + z3*z1) - sq(r); //We can solve with the quadratic equation (there may be 2 real solutions) //The discriminant (part under the radical in the quad.eq.) will tell us how many. double discrim; discrim = sq(b) - 4*a*c; double sqrt_discrim = Math.sqrt(discrim); //If the discriminant is less than zero, there was no collision if( discrim < 0 ) return null; //If the discriminant is zero, there is one soln. (w/ multiplicity 2) else if( discrim == 0 ) { double u = -b/(2*a); return p1.add( (p2.sub(p1)).mul(u) ); } //Otherwise, there are two solutions, pick the one w/ parameter closest to p1 else { double u1 = ( -b - sqrt_discrim) / (2*a); double u2 = ( -b + sqrt_discrim) / (2*a); //since sqrt_disc is > 0, I think this will always be u1? double u = min(u1, u2); return p1.add( (p2.sub(p1)).mul(u) ); } } //function counterClockwise //Determines if the given polygon (in screen space) has counterclockwise winding boolean counterClockwise(Polygon poly) { //We grab three points, and use the relation of the third to the // line formed by the first two to determine winding //Specifically, is the determinant of the matrix consisting of the // second and third vector translated to originate from the first // positive or negative //More specifically, is (p1_x-p0_x) * (p2_y-p0_y) > (p2_x-p0_x) * (p1_y-p0_y) // if so, it is ccw int x0 = poly.xpoints[0]; int y0 = poly.ypoints[0]; int x1 = poly.xpoints[1]; int y1 = poly.ypoints[1]; int x2 = poly.xpoints[2]; int y2 = poly.ypoints[2]; return (x1-x0)*(y2-y0) > (x2-x0)*(y1-y0); //i really think this is correct } //Square final double sq(double x) { return x*x; } //Maximum final double max(double a, double b) { return (a > b ? a : b); } //Minimum final double min(double a, double b) { return (a < b ? a : b); } //double color value [0.0,1.0] --> integer color value [0,255] final int d2i(double c) { return (int)max(0.0, min(255, 255*c)); } //extracts an rgb color component, in double [0.0, 1.0] final double getRGB(Color color, int i) { switch(i) { case 0: return color.getRed()/255.0; case 1: return color.getGreen()/255.0; case 2: return color.getBlue()/255.0; default: throw new RuntimeException("Passed a valid color component"); } } //Current transform matrix, which takes world coord to screen coord private Matrix4x4 transformMatrix = new Matrix4x4(); //Current transform-matrix stack, the previous var points to the head of this private Stack transformStack = new Stack(); //Current Render state; which primitive, if any, is being rendered private int renderState = RENDER_NONE; //Storage for any polygon-based primitive being submitted Polygon polygon = new Polygon(); //Storage for the last coordinate for a line-based primitive private int lineX= 0; private int lineY= 0; //Counts the number of vertices sent for the current primitive private int sentVertices = 0; //Current rendering context private Graphics graphics = null; //Do we cull faces? boolean cullPolygon=true; //Do we have lighting? boolean lighting=false; //Size for rendering points int pointSize=1; //Currently set normal (we only have face normals for now) Vector3 currentNormal=new Vector3(0,1,0); //Information used for lighting (we have a diffuse lighting model) Vector3 lightPos=new Vector3(0,1,0); Color ambientColor=new Color(0,64,127); Color diffuseColor=new Color(0,64,127); Color ambientMaterial=new Color(255,255,255); Color diffuseMaterial=new Color(255,255,255); double ambientCoeff=0.2; double diffuseCoeff=0.6; } class GenericApplet extends java.applet.Applet implements Runnable { public boolean damage = true; // you can force a render public void render(Graphics g) { } // you can define how to render private Image image = null; private Graphics buffer = null; private Thread t; private Rectangle r = new Rectangle(0, 0, 0, 0); public void start() { if (t == null) { //System.out.println("Got here start"); t = new Thread(this); t.start(); } } public void stop() { //System.out.println("Got here stop"); if (t != null) { t.stop(); t = null; } } public void run() { //System.out.println("Got here run"); try { while (true) { repaint(); t.sleep(30); } } catch(InterruptedException e){}; } public void update(Graphics g) { //System.out.println("Got here update"); if (r.width != bounds().width || r.height != bounds().height) { image = createImage(bounds().width, bounds().height); buffer = image.getGraphics(); r = bounds(); damage = true; } render(buffer); //damage = false; if (image != null) g.drawImage(image,0,0,this); } }