import java.awt.*; import Geometry.*; import java.util.*; import java.net.*; import java.io.*; public class ThreeDimensionalApplet extends PixApplet { int x = 100, y = 100; int coords[][] = new int[4][]; double normals[][] = new double[4][]; int colors[][] = new int[4][]; int currentColor[] = new int[4]; int texCoords[][] = new int[4][]; ThreeDimensionalApplet() { for(int i=0; i RENDER_LAST ) { throw new RuntimeException("Attempted to draw an invalid primitive"); } if( wireframeMode && shapeType == RENDER_TRIANGLE ) renderState = RENDER_OPEN_TRIANGLE; else if( wireframeMode && shapeType == RENDER_QUAD ) renderState = RENDER_OPEN_QUAD; else 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 color3i(int r, int g, int b) { //graphics.setColor(new Color((int)(r*255.0), (int)(g*255.0), (int)(b*255.0))); color4i(r, g, b, 255); } void color3d(double r, double g, double b) { //graphics.setColor(new Color((int)(r*255.0), (int)(g*255.0), (int)(b*255.0))); color4i(d2i(r), d2i(g), d2i(b), 255); } void color4d(double r, double g, double b, double a) { color4i(d2i(r), d2i(g), d2i(b), d2i(a)); } void color4i(int r, int g, int b, int a) { //graphics.setColor(new Color(r,g,b)); currentColor[0] = r; currentColor[1] = g; currentColor[2] = b; currentColor[3] = a; colors[sentVertices][0] = r; colors[sentVertices][1] = g; colors[sentVertices][2] = b; colors[sentVertices][3] = a; } void color(Color color) { color3i(color.getRed(), color.getGreen(), color.getBlue()); } //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 double rgba[] = new double[4]; int[] texRgba = new int[4]; double[] dstRgba = new double[4]; 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 double rgba[] = this.rgba; int texRgba[] = this.texRgba; double dstRgba[] = this.dstRgba; int pix[] = this.pix; int abuffer[] = this.abuffer; 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)) ); double focal_length = 300; screenX = (int)(focal_length * screenCoord.x() / (focal_length - doubleZ) + W/2 ) ; screenY = (int)(-focal_length * screenCoord.y() / (focal_length - doubleZ) + H/2 ); //screenZ = (int)screenCoord.z(); } //lighting = true; if( !lighting ) { colors[sentVertices][0] = currentColor[0]; colors[sentVertices][1] = currentColor[1]; colors[sentVertices][2] = currentColor[2]; colors[sentVertices][3] = currentColor[3]; } else { Color col = computeLightedColor(); colors[sentVertices][0] = col.getRed(); colors[sentVertices][1] = col.getGreen(); colors[sentVertices][2] = col.getBlue(); colors[sentVertices][3] = currentColor[3]; //don't use lighting for alpha } //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 ) { if( screenX < 0 || screenX > W ) return; else if( screenY < 0 || screenY > H ) return; //For points, just draw an appropriatly sized box on every vertex() //if(lighting) graphics.setColor(computeLightedColor()); //graphics.fillRect(screenX-pointSize/2, screenY-pointSize/2, pointSize, pointSize); int lowI=screenX-thePointSize/2; int highI=screenX+thePointSize/2; int lowJ=screenY-thePointSize/2; int highJ=screenY+thePointSize/2; int height = currentTexture.height; int width = currentTexture.width; int texS=0, texT=0; //double dS, dT; //dS = 1.0/(highI - lowI)*(double)height; //dT = 1.0/(highJ - lowJ)*(double)width; for(int i=lowI; i= currentTexture.width*currentTexture.height ) texIdx = 0; PixApplet.unpack(texRgba, currentTexture.colors[texIdx]); //rgba[3] = rgba[3] * i2d(texRgba[3]); //for(int k=0; k<3; k++) // rgba[k] = rgba[k]*i2d(texRgba[k]); rgba[3] = i2d(texRgba[0]); //for(int k=0; k<3; k++) // rgba[k] = i2d(texRgba[k]); } //Blend the incoming fragment with destination pixel if( this.blending ) { PixApplet.unpackDouble(dstRgba, pix[pixIdx]); dstRgba[3] = abuffer[pixIdx]; computeBlend(rgba, dstRgba, rgba); } pix[pixIdx] = PixApplet.pack(d2i(rgba[0]), d2i(rgba[1]), d2i(rgba[2])); abuffer[pixIdx] = d2i(rgba[3]); //texT += dT; } //for j //texS += dS; } //for I } 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 || renderState == RENDER_QUAD || renderState == RENDER_OPEN_QUAD ) { //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 ); } sentVertices = 0; } //Draw a quad on every fourth vertex if( renderState == RENDER_OPEN_QUAD && sentVertices == 4 ) { //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, false, coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], 0, 0, coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], 0, 0, coords[3][0], coords[3][1], coords[3][2], colors[3][0], colors[3][1], colors[3][2], 0, 0 ); } sentVertices = 0; } //Draw a quad on every fourth vertex else if( renderState == RENDER_QUAD && sentVertices == 4 ) { //perform back face culling if(!cullPolygon || counterClockwise(coords)) { //if(lighting) graphics.setColor(computeLightedColor()); //graphics.drawPolygon(polygon); drawTriangle(true, 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(true, false, coords[0][0], coords[0][1], coords[0][2], colors[0][0], colors[0][1], colors[0][2], 0, 0, coords[2][0], coords[2][1], coords[2][2], colors[2][0], colors[2][1], colors[2][2], 0, 0, coords[3][0], coords[3][1], coords[3][2], colors[3][0], colors[3][1], colors[3][2], 0, 0 ); } 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 } Matrix4x4 projectionMatrix = new Matrix4x4(); Matrix4x4 transProjMatrix = new Matrix4x4(); void projection(Matrix4x4 proj) { projectionMatrix = projectionMatrix.mul(proj); transProjMatrix = projectionMatrix.mul(transformMatrix); } //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; } boolean wireframeMode = false; void wireframe(boolean doWire) { wireframeMode = doWire; } //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_QUAD=7; static final int RENDER_OPEN_QUAD=8; static final int RENDER_POINTS=9; 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; static final int RENDER_BLENDING=259; //Blending settings static final int RENDER_ONE = 512; static final int RENDER_SRC_ALPHA = 513; static final int RENDER_ONE_MINUS_SRC_ALPHA = 514; static final int RENDER_DST_ALPHA = 515; static final int RENDER_ONE_MINUS_DST_ALPHA = 516; static final int RENDER_ZERO = 517; int blendSrc=RENDER_ONE; int blendDst=RENDER_ZERO; double[] tempSrc = new double[4]; double[] tempDst = new double[4]; void computeBlend(double[] src, double dst[], double out[]) { tempSrc = this.tempSrc; tempDst = this.tempDst; //Compute src contribution switch(blendSrc) { case RENDER_ONE: for(int k=0; k<4; k++) tempSrc[k] = src[k]; break; case RENDER_SRC_ALPHA: for(int k=0; k<4; k++) tempSrc[k] = src[k] * src[3]; break; case RENDER_ONE_MINUS_SRC_ALPHA: for(int k=0; k<4; k++) tempSrc[k] = src[k] * (1-src[3]); break; case RENDER_ZERO: for(int k=0; k<4; k++) tempSrc[k] = 0; break; default: throw new RuntimeException("Illegal Blending Mode"); } //Compute dst contribution switch(blendDst) { case RENDER_ONE: for(int k=0; k<4; k++) tempDst[k] = dst[k]; break; case RENDER_SRC_ALPHA: for(int k=0; k<4; k++) tempDst[k] = dst[k] * src[3]; break; case RENDER_ONE_MINUS_SRC_ALPHA: for(int k=0; k<4; k++) tempDst[k] = dst[k] * (1-src[3]); break; case RENDER_ZERO: for(int k=0; k<4; k++) tempDst[k] = 0; break; default: throw new RuntimeException("Illegal Blending Mode"); } //Sum the final output for(int k=0; k<4; k++) out[k] = min( tempSrc[k] + tempDst[k] , 1.0 ); return; } 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; case RENDER_BLENDING: blending = 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; case RENDER_BLENDING: blending = 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) { thePointSize = 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[]{255,255,255}; 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=-1, texWidth=-1, colors[]=null; if( texturing ) { texHeight = currentTexture.height; texWidth = currentTexture.width; 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]); if( texturing ) { 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)); } else { pix[idx] = pack((int)(rNow), (int)(gNow), (int)(bNow)); } zbuffer[idx] = zInt; } //} //catch( ArrayIndexOutOfBoundsException e ) { return; } //End draw zNow += ddz; rNow += ddr; gNow += ddg; bNow += ddb; sNow += dds; tNow += ddt; } } else //wire framed { //pix[xy2i(i,(int)yUp)] = pack((int)rUp, (int)gUp, (int)bUp); //pix[xy2i(i,(int)yDown)] = pack((int)rDown, (int)gDown, (int)bDown); //int zInt = (int)(zNow); int idx; idx = xy2i(i,(int)yUp); if( zbuffer[idx] > (int)zUp ) pix[idx] = pack((int)rUp, (int)gUp, (int)bUp); idx = xy2i(i,(int)yDown); if( zbuffer[idx] > (int)zDown ) pix[idx] = pack((int)rDown, (int)gDown, (int)bDown); } } 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]; if( texturing ) { 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) ); } else { pix[idx] = pack((int)(rNow), (int)(gNow), (int)(bNow) ); } 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; } int idx; idx = xy2i(i,(int)yUp); if( zbuffer[idx] > (int)zUp ) pix[idx] = pack((int)rUp, (int)gUp, (int)bUp); idx = xy2i(i,(int)yDown); if( zbuffer[idx] > (int)zDown ) pix[idx] = pack((int)rDown, (int)gDown, (int)bDown); } } 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(0,0,0); 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 ); } void blendFunc(int src, int dst) { if( src > RENDER_ZERO || src < RENDER_ONE ) throw new RuntimeException("Invalid src blend mode"); if( dst > RENDER_ZERO || dst < RENDER_ONE ) throw new RuntimeException("Invalid dst blend mode"); blendSrc = src; blendDst = dst; } boolean myInit=false; Bitmap flare; public void render() { if(myInit == false) { flare = readTexture(openModel("http://www.cat.nyu.edu/~cdecoro/HWFinal/flare.bmp")); myInit = true; } 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); */ texture(flare); enable(RENDER_BLENDING); disable(RENDER_CULL_FACE); //enable(RENDER_TEXTURE); blendFunc(RENDER_SRC_ALPHA, RENDER_ONE_MINUS_SRC_ALPHA); pointSize(128); begin(RENDER_POINTS); color4i(200, 10, 0, 120); vertex(200, 200, 0); color4i(0, 10, 200, 120); vertex(250, 250, 0); end(); begin(RENDER_QUAD); color4i(10, 200, 20, 180); vertex(300, 300, 0); vertex(400, 300, -5); vertex(400, 400, -5); vertex(300, 400, 0); end(); begin(RENDER_QUAD); color4i(10, 200, 20, 180); vertex(-200, -200, 0); vertex(+200, -200, 0); vertex(+200, +200, -50); vertex(-200, +200, -50); end(); } //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)); } //integer color value [0,255] --> double color value [0.0,1.0] final double i2d(double c) { return max(0, min(255, c)) / 255.0; } //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"); } } //Given a URL, opens an Internet connection and returns a stream public 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; } public 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