//======================================================== // 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(); model.tris[tri].texID = texID; 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 vtx.texCoord[0] = tok.nval; tok.nextToken(); vtx.texCoord[1] = tok.nval; 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=false; boolean doLighting=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 '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; } 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(); } } class ThreeDimensionalApplet extends PixApplet { int x = 100, y = 100; int coords[][] = new int[3][]; double normals[][] = new double[3][]; int colors[][] = new int[3][]; int currentColor[] = new int[3]; int texCoords[][] = new int[3][]; ThreeDimensionalApplet() { for(int i=0; i 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)); currentColor[0] = r; currentColor[1] = g; currentColor[2] = b; colors[sentVertices][0] = r; colors[sentVertices][1] = g; colors[sentVertices][2] = 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 = false; int screenX, screenY; int screenZ; //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]; screenZ = (int)transCoord[2]; } 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(); double doubleZ = screenCoord.z(); screenZ = (int)( Integer.MAX_VALUE*((doubleZ - near)/(far - near)) ); //screenZ = (int)screenCoord.z(); } //lighting = true; if( !lighting ) { colors[sentVertices][0] = currentColor[0]; colors[sentVertices][1] = currentColor[1]; colors[sentVertices][2] = currentColor[2]; } else { Color col = computeLightedColor(); colors[sentVertices][0] = col.getRed(); colors[sentVertices][1] = col.getGreen(); colors[sentVertices][2] = col.getBlue(); } //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); //coords[sentVertices] = new Vector3(screenX, screenY, screenZ); coords[sentVertices][0] = screenX; coords[sentVertices][1] = screenY; coords[sentVertices][2] = screenZ; sentVertices++; } //Draw a triangle on every third vertex if( renderState == RENDER_TRIANGLE && sentVertices == 3 ) { //Perform back face culling if(!cullPolygon || counterClockwise(coords)) { //if(lighting) graphics.setColor(computeLightedColor()); //graphics.fillPolygon(polygon); /* drawTriangle(true, polygon.xpoints[0], polygon.ypoints[0], 0, 0, 0, 0, 0, 0, polygon.xpoints[1], polygon.ypoints[1], 0, 0, 0,0, 0, 0, polygon.xpoints[2], polygon.ypoints[2], 0, 0, 0, 0, 0, 0); */ drawTriangle(true, true, coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], texCoords[0][0], texCoords[0][1], coords[1][0], coords[1][1], coords[1][2], colors[1][0], colors[1][1], colors[1][2], texCoords[1][0], texCoords[1][1], coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], texCoords[2][0], texCoords[2][1] ); } polygon = new Polygon(); sentVertices = 0; } else if( renderState == RENDER_OPEN_TRIANGLE && sentVertices == 3 ) { //perform back face culling if(!cullPolygon || counterClockwise(coords)) { //if(lighting) graphics.setColor(computeLightedColor()); //graphics.drawPolygon(polygon); drawTriangle(false, false, coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], 0, 0, coords[1][0], coords[1][1], coords[1][2], colors[1][0], colors[1][1], colors[1][2], 0, 0, coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], 0, 0 ); /* drawTriangle(false, polygon.xpoints[0], polygon.ypoints[0], 0, 0, 0, 0, 0, 0, polygon.xpoints[1], polygon.ypoints[1], 0, 0, 0,0, 0, 0, polygon.xpoints[2], polygon.ypoints[2], 0, 0, 0, 0, 0, 0); */ } //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 ortho //Sets up an orthoganl projection //Right now, only the near and far args have effect void ortho(double _near, double _far) { near = _near; far = _far; zInterval = (far - near) / (Integer.MAX_VALUE / 2); } double near=-1000; double far=1000; double zInterval = (far - near) / (Integer.MAX_VALUE / 2); //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; static final int RENDER_TEXTURE=258; void enable(int param) { switch(param) { case RENDER_CULL_FACE: cullPolygon = true; break; case RENDER_LIGHTING: lighting = true; break; case RENDER_TEXTURE: texturing = 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; case RENDER_TEXTURE: texturing = 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 texture //Sets the currently used texture - texturing must be enabled seperatly void texture(Bitmap bmp) { currentTexture = bmp; } void texCoord(double s, double t) { texCoords[sentVertices][0] = (int)(s*currentTexture.width); texCoords[sentVertices][1] = (int)(t*currentTexture.height); } //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(int 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]; */ int x0 = (int)poly[0][0]; int y0 = (int)poly[0][1]; int x1 = (int)poly[1][0]; int y1 = (int)poly[1][1]; int x2 = (int)poly[2][0]; int y2 = (int)poly[2][1]; //This way seems to work, though it is the opposite of what we had before return (x1-x0)*(y2-y0) < (x2-x0)*(y1-y0); } int rgb[] = new int[3]; void drawTriangle( boolean filled, boolean textured, int x1, int y1, int z1, int r1, int g1, int b1, int s1, int t1, int x2, int y2, int z2, int r2, int g2, int b2, int s2, int t2, int x3, int y3, int z3, int r3, int g3, int b3, int s3, int t3) { int pix[] = this.pix; int zbuffer[] = this.zbuffer; int xlow, xmid, xhigh; int ylow, ymid, yhigh; int zlow, zmid, zhigh; int rlow, rmid, rhigh; int glow, gmid, ghigh; int blow, bmid, bhigh; int slow, smid, shigh; int tlow, tmid, thigh; if( x1 < 0 && x2 < 0 && x3 < 0 ) return; if( y1 < 0 && y2 < 0 && y3 < 0 ) return; if( x1 < x2 ) { if( x1 < x3 ) { if( x2 < x3 ) //1,2, 3 { xlow = x1; ylow = y1; zlow = z1; rlow = r1; glow = g1; blow = b1; slow = s1; tlow = t1; xmid = x2; ymid = y2; zmid = z2; rmid = r2; gmid = g2; bmid = b2; smid = s2; tmid = t2; xhigh = x3; yhigh = y3; zhigh = z3; rhigh = r3; ghigh = g3; bhigh = b3; shigh = s3; thigh = t3; } else //1, 3, 2 { xlow = x1; ylow = y1; zlow = z1; rlow = r1; glow = g1; blow = b1; slow = s1; tlow = t1; xmid = x3; ymid = y3; zmid = z3; rmid = r3; gmid = g3; bmid = b3; smid = s3; tmid = t3; xhigh = x2; yhigh = y2; zhigh = z2; rhigh = r2; ghigh = g2; bhigh = b2; shigh = s2; thigh = t2; } } else //order is 3, 1, 2 { xlow = x3; ylow = y3; zlow = z3; rlow = r3; glow = g3; blow = b3; slow = s3; tlow = t3; xmid = x1; ymid = y1; zmid = z1; rmid = r1; gmid = g1; bmid = b1; smid = s1; tmid = t1; xhigh = x2; yhigh = y2; zhigh = z2; rhigh = r2; ghigh = g2; bhigh = b2; shigh = s2; thigh = t2; } } else // y2 < y1 { if( x1 < x3 ) //order is 2, 1, 3 { xlow = x2; ylow = y2; zlow = z2; rlow = r2; glow = g2; blow = b2; slow = s2; tlow = t2; xmid = x1; ymid = y1; zmid = z1; rmid = r1; gmid = g1; bmid = b1; smid = s1; tmid = t1; xhigh = x3; yhigh = y3; zhigh = z3; rhigh = r3; ghigh = g3; bhigh = b3; shigh = s3; thigh = t3; } else { if( x2 < x3 ) //2, 3, 1 { xlow = x2; ylow = y2; zlow = z2; rlow = r2; glow = g2; blow = b2; slow = s2; tlow = t2; xmid = x3; ymid = y3; zmid = z3; rmid = r3; gmid = g3; bmid = b3; smid = s3; tmid = t3; xhigh = x1; yhigh = y1; zhigh = z1; rhigh = r1; ghigh = g1; bhigh = b1; shigh = s1; thigh = t1; } else //3, 2, 1 { xlow = x3; ylow = y3; zlow = z3; rlow = r3; glow = g3; blow = b3; slow = s3; tlow = t3; xmid = x2; ymid = y2; zmid = z2; rmid = r2; gmid = g2; bmid = b2; smid = s2; tmid = t2; xhigh = x1; yhigh = y1; zhigh = z1; rhigh = r1; ghigh = g1; bhigh = b1; shigh = s1; thigh = t1; } } } //====================================================================== // We have sorted, now draw the first triangle //====================================================================== double runAB = xmid - xlow; double runBC = xhigh - xmid; double runAC = xhigh - xlow; //Compute the slope of the upper line segment //Also compute the slopes of r,g,b on this segment //The key is that we want all dX1 to be on the upper line, // and dX2 on the lower line double dy1, dz1, dr1, dg1, db1, ds1, dt1; double dy2, dz2, dr2, dg2, db2, ds2, dt2; dy1 = (ymid - ylow) / runAB; dy2 = (yhigh - ylow) / runAC; //If the above holds, then it will be the dX1 that will hit the other vtx, // and that will have to change int changes = 1; if( dy1 > dy2 ) { //if dy1 > dy2, all is good, assign the others //This means that the line from the low to mid is above line from low to high //dy1 = (ymid - ylow) / runAB; dz1 = (zmid - zlow) / runAB; dr1 = (rmid - rlow) / runAB; dg1 = (gmid - glow) / runAB; db1 = (bmid - blow) / runAB; ds1 = (smid - slow) / runAB; dt1 = (tmid - tlow) / runAB; //then compute the slopes for the lower segment //dy2 = (yhigh - ylow) / runAC; dz2 = (zhigh - zlow) / runAC; dr2 = (rhigh - rlow) / runAC; dg2 = (ghigh - glow) / runAC; db2 = (bhigh - blow) / runAC; ds2 = (shigh - slow) / runAC; dt2 = (thigh - tlow) / runAC; } else { //If dy1 is not > dy2, this is bad. Swap and then assign stuff dy2 = (ymid - ylow) / runAB; dz2 = (zmid - zlow) / runAB; dr2 = (rmid - rlow) / runAB; dg2 = (gmid - glow) / runAB; db2 = (bmid - blow) / runAB; ds2 = (smid - slow) / runAB; dt2 = (tmid - tlow) / runAB; dy1 = (yhigh - ylow) / runAC; dz1 = (zhigh - zlow) / runAC; dr1 = (rhigh - rlow) / runAC; dg1 = (ghigh - glow) / runAC; db1 = (bhigh - blow) / runAC; ds1 = (shigh - slow) / runAC; dt1 = (thigh - tlow) / runAC; //Now, the other path will hit the new vtx changes = 2; } //Draw the left triangle double yUp=ylow, yDown=ylow; double zUp=zlow, zDown=zlow; double rUp=rlow, rDown=rlow; double gUp=glow, gDown=glow; double bUp=blow, bDown=blow; double sUp=slow, sDown=slow; double tUp=tlow, tDown=tlow; /* drawHalfTriangle(filled, xlow, xmid, yUp, yDown, dy1, dy2, zUp, zDown, dz1, dz2, rUp, rDown, dr1, dr2, gUp, gDown, dg1, dg2, bUp, bDown, db1, db2 ); */ int texHeight = currentTexture.height; int texWidth = currentTexture.width; int colors[] = currentTexture.colors; //try //{ for(int i=(int)xlow; i<(int)xmid; i++) { double ddz, ddr, ddg, ddb, dds, ddt; if( yUp - yDown != 0 ) { ddz = (zUp - zDown) / (yUp - yDown); ddr = (rUp - rDown) / (yUp - yDown); ddg = (gUp - gDown) / (yUp - yDown); ddb = (bUp - bDown) / (yUp - yDown); dds = (sUp - sDown) / (yUp - yDown); ddt = (tUp - tDown) / (yUp - yDown); double zNow=zDown, rNow=rDown, gNow=gDown, bNow=bDown; double sNow=sDown, tNow=tDown; if( filled ) { for(int j=(int)yDown; j<(int)yUp+1; j++) { //Draw the stuff here //try //{ int idx = xy2i(i,j); int texIdx = (int)tNow*texWidth + (int)sNow; if( texIdx < 0 ) texIdx = 0; if( texIdx >= texWidth*texHeight ) texIdx = 0; //int zInt = (int)Math.round(zNow); int zInt = (int)(zNow); if( zbuffer[idx] > zInt ) { //pix[idx] = pack((int)rNow, (int)gNow, (int)bNow); //pix[idx] = (colors[texIdx]*rNow)>>1; //unpack(rgb, colors[texIdx]); unpack(rgb, colors[texIdx]); pix[idx] = pack((int)(rNow * rgb[0]/255.0), (int)(gNow * rgb[1]/255.0), (int)(bNow * rgb[2]/255.0)); zbuffer[idx] = zInt; } //} //catch( ArrayIndexOutOfBoundsException e ) { return; } //End draw zNow += ddz; rNow += ddr; gNow += ddg; bNow += ddb; sNow += dds; tNow += ddt; } } else //wire framed { //try //{ pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp); pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown); //} //catch( ArrayIndexOutOfBoundsException e ) { return; } } } yUp += dy1; yDown += dy2; zUp += dz1; zDown += dz2; rUp += dr1; rDown += dr2; gUp += dg1; gDown += dg2; bUp += db1; bDown += db2; sUp += ds1; sDown += ds2; tUp += dt1; tDown += dt2; } if( xlow == xmid ) { if( ylow > ymid ) { yUp = ylow; rUp = rlow; gUp = glow; bUp = blow; sUp = slow; tUp = tlow; yDown = ymid; rDown = rmid; gDown = gmid; bDown = bmid; sDown = smid; tDown = tmid; } else { yDown = ylow; rDown = rlow; gDown = glow; bDown = blow; sDown = slow; tDown = tlow; yUp = ymid; rUp = rmid; gUp = gmid; bUp = bmid; sUp = smid; tUp = tmid; } } //======================================================= // We have now drawn the first triangle, draw second //======================================================= //If the runBC is zero, we dont have a second triangle to draw //We know that the line from (yUp->end) is above line (yDown->end) //This eliminates some of the extra tests //For one of the two lines, we just continue as before //For the other, we have the new vertex to deal with if( changes == 1 ) { dy1 = (yhigh - ymid) / runBC; dz1 = (zhigh - zmid) / runBC; dr1 = (rhigh - rmid) / runBC; dg1 = (ghigh - gmid) / runBC; db1 = (bhigh - bmid) / runBC; ds1 = (shigh - smid) / runBC; dt1 = (thigh - tmid) / runBC; } else { //then compute the slopes for the lower segment dy2 = (yhigh - ymid) / runBC; dz2 = (zhigh - zmid) / runBC; dr2 = (rhigh - rmid) / runBC; dg2 = (ghigh - gmid) / runBC; db2 = (bhigh - bmid) / runBC; ds2 = (shigh - smid) / runBC; dt2 = (thigh - tmid) / runBC; } //Draw the right triangle //yUp, yDown, etc. stay as they were - we have changed the slope /* drawHalfTriangle(filled, xmid, xhigh, yUp, yDown, dy1, dy2, zUp, zDown, dz1, dz2, rUp, rDown, dr1, dr2, gUp, gDown, dg1, dg2, bUp, bDown, db1, db2 ); */ for(int i=(int)xmid; i<(int)xhigh+1; i++) { double ddz, ddr, ddg, ddb, dds, ddt; if( yUp - yDown != 0 ) { ddz = (zUp - zDown) / (yUp - yDown); ddr = (rUp - rDown) / (yUp - yDown); ddg = (gUp - gDown) / (yUp - yDown); ddb = (bUp - bDown) / (yUp - yDown); dds = (sUp - sDown) / (yUp - yDown); ddt = (tUp - tDown) / (yUp - yDown); double zNow=zDown, rNow=rDown, gNow=gDown, bNow=bDown, sNow=sDown, tNow=tDown; if( filled ) { for(int j=(int)yDown; j<(int)yUp+1; j++) { //Draw the stuff here //try //{ int idx = xy2i(i,j); int zInt = (int)Math.round(zNow); int texIdx = (int)tNow*texWidth + (int)sNow; if( texIdx < 0 ) texIdx = 0; if( texIdx >= texWidth*texHeight ) texIdx = 0; if( zbuffer[idx] > zInt ) { //pix[idx] = pack((int)rNow, (int)gNow, (int)bNow); //pix[idx] = colors[texIdx]; unpack(rgb, colors[texIdx]); pix[idx] = pack((int)(rNow * rgb[0]/255.0), (int)(gNow * rgb[1]/255.0), (int)(bNow * rgb[2]/255.0)); zbuffer[idx] = zInt; } //} //catch( ArrayIndexOutOfBoundsException e ) { return; } //End draw zNow += ddz; rNow += ddr; gNow += ddg; bNow += ddb; sNow += dds; tNow += ddt; } } else //wire framed { //try //{ pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp); pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown); //} //catch( ArrayIndexOutOfBoundsException e ) { return; } } } yUp += dy1; yDown += dy2; zUp += dz1; zDown += dz2; rUp += dr1; rDown += dr2; gUp += dg1; gDown += dg2; bUp += db1; bDown += db2; sUp += ds1; sDown += ds2; tUp += dt1; tDown += dt2; } //} //catch( ArrayIndexOutOfBoundsException e ) //{ // return; //}; } void clear() { int[] pix = this.pix; int value = pack(200,200,200); int zvalue = Integer.MAX_VALUE; for(int i=0; i zNow ) //{ pix[idx] = pack((int)rNow, (int)gNow, (int)bNow); //zbuffer[idx] = zNow; //} } catch( ArrayIndexOutOfBoundsException e ) { return; } //End draw zNow += ddz; rNow += ddr; gNow += ddg; bNow += ddb; } } else //wire framed { try { pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp); pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown); } catch( ArrayIndexOutOfBoundsException e ) { return; } } } yUp += dy1; yDown += dy2; zUp += dz1; zDown += dz2; rUp += dr1; rDown += dr2; gUp += dg1; gDown += dg2; bUp += db1; bDown += db2; } } void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { drawTriangle(true, false, x1, y1, 0, 255, 0, 0, 0, 0, x2, y2, 0, 0, 255, 0, 0, 0, x3, y3, 0, 0, 0, 255, 0, 0 ); } public void render() { clear(); drawTriangle(10, 10, 60, 10, 30, 30); //No side aligned drawTriangle(30, 60, 10, 100, 50, 50); drawTriangle(300, 300, 500, 400, 560, 50); //Left side y aligned drawTriangle(10, 100, 10, 150, 50, 120); } //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; //Do we have texturing? boolean texturing=false; //The currently assigned texture Bitmap currentTexture=null; //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); } }