/* * Alec Jacobson * */ import java.awt.*; import java.util.LinkedList; import java.util.ArrayList; import java.util.PriorityQueue; /** * * @author ajx */ /** * Class to hold a character * */ class Character implements Comparable{ double x,y,z;//location of character double rotZ, rotX, rotY; double sx = 1.; double sy = 1.; double sz = 1.; Point velocity; boolean wasDrawn = false;//was the character drawn last time around LinkedList s_list;//list of surfaces Matrix4x4 mat; public Character(){ init(); } public void init(){ //init matrix for machine as whole mat = new Matrix4x4(); mat.identity(); s_list = new LinkedList(); //make individual parts here } /**go through all of the surfaces and update matrices*/ public void updateMatrices(){ this.mat.identity(); this.mat.translate(x,y,z); this.mat.rotateZ(rotZ); this.mat.rotateX(rotX); this.mat.rotateY(rotY); this.mat.scale(sx ,sy, sz); //update matrices of all surfaces for(Surface s: s_list) s.matrix = mat.getCopy(); } public void applyVelocity(double dt){ x += dt*velocity.x; y += dt*velocity.y; z += dt*velocity.z; rotZ += dt*Math.PI/32*velocity.y+dt*Math.PI/32*velocity.x; rotX += -dt*Math.PI/32*velocity.y-dt*Math.PI/32*velocity.z; rotY += dt*Math.PI/32*velocity.z+dt*Math.PI/32*velocity.x; } public void addToVelocity(double x, double y, double z){ velocity.x += x; velocity.y += y; velocity.z += z; } public void multiplyToVelocity(double x, double y, double z){ velocity.x *= x; velocity.y *= y; velocity.z *= z; } public int compareTo(Object o){ Double t = this.z; Double s = ( (Character) o).z; return t.compareTo(s); } } /** * Bouncey Ball */ class Ball extends Character{ int lat; int lon; int radius; Color color; public Ball(int a, int o, int r){ lat = a; lon = o; radius = r; init(); } public void init(){ //init matrix for machine as whole mat = new Matrix4x4(); mat.identity(); //velocity to zero velocity = new Point(0.,0.,0.); s_list = new LinkedList(); //make individual parts here s_list.add( new Sphere(lat, lon)); } /**go through all of the surfaces and update matrices*/ public void updateMatrices(){ this.mat.identity(); this.mat.translate(x,y,z); this.mat.scale(sx*radius ,sy*radius, sz*radius); this.mat.rotateZ(rotZ); this.mat.rotateX(rotX); this.mat.rotateY(rotY); //update matrices of all surfaces for(Surface s: s_list) s.matrix = mat.getCopy(); } public void setColor(Color c){ color = c; for(Surface s: s_list) s.color = color; } } /** class to hold surface info**/ class Surface{ ArrayList v_list;//vertex list LinkedList f_list;//facelist Matrix4x4 matrix; Color color; public Surface(){ v_list = new ArrayList(); f_list = new LinkedList(); matrix = new Matrix4x4(); matrix.identity();//start with identity color = Color.WHITE;//just in case color is not later defined } public void addVertex(Point p){ v_list.add(p); } //assume distinct is all false public void addFace(Point[] a){ boolean[] distinct = new boolean[a.length];//default false addFace(a, distinct); } public void addFace(Point[] a, boolean[] distinct){ int[] face = new int[a.length]; boolean[] found = new boolean[a.length]; for(int i = 0; i < v_list.size(); i++){ for(int j = 0; j < a.length; j++){ if(!distinct[j]&&a[j].equals(v_list.get(i))){ face[j] = i; found[j] = true; } } } //add all the vertices which didn't already exist for( int j = 0; j< a.length;j++){ if(!found[j]){ int i = v_list.size(); v_list.add(a[j]); face[j] = i; } } f_list.add(face); } } class Sphere extends Surface{ public Sphere(int lat, int lon){ v_list = new ArrayList(); f_list = new LinkedList(); matrix = new Matrix4x4(); matrix.identity();//start with identity color = Color.WHITE;//just in case color is not later defined make(lat, lon); } private void make(int lat, int lon){ //make top and bottom Point[] front = new Point[lon]; Point[] back = new Point[lon]; double theta = 2*Math.PI/lon;//change in angle double x = 1; double y = 0; double z = Math.cos(Math.PI/(lat+1)); double w = Math.sin(Math.PI/(lat+1)); //make front and back for(int j = 0; j(); f_list = new LinkedList(); matrix = new Matrix4x4(); matrix.identity();//start with identity color = Color.WHITE;//just in case color is not later defined make(n); } private void make(int n){ Point[] front = new Point[n]; Point[] back = new Point[n]; double theta = 2*Math.PI/n;//change in angle double x = 1; double y = 0; double z = 1; //make front and back for(int j = 0; j faceQ; public void initVals(){ scale = 20; zoom = 20; time0 = System.currentTimeMillis() / 1000.0; //gravity //construct characters ball_1 = new Ball( (int)(res*100), (int)(2*res*100), 1); ball_2 = new Ball( (int)(1.5*res*100), (int)(2*1.5*res*100), 2); ball_3 = new Ball( (int)(1.5*res*100), (int)(2*1.5*res*100), 2); ball_3.setColor(Color.YELLOW); ball_2.setColor(Color.PINK); score_2 = new Character(); score_3 = new Character(); //send characaters behind focal length here if(rightWon) ball_1.x = 20.0; else ball_1.x = -20.0; ball_1.y = -20.0; ball_2.x = -20.0; ball_3.x = 20.0; //set up a camera setCamera(); } public void setCamera(){ camera = new Matrix4x4(); camera.identity(); camera.translate(w/2,2*h/3,0); camera.scale(scale, scale, scale);//zoom in 100x } public void render(Graphics g) { if (true||w == 0) {//true here for resizing appletviewer w = bounds().width; h = bounds().height; if(uninit){ initVals(); uninit = false; } } if(scoreSeq>0){ int u,v; if(rightWon){ u = 0; v = w/2; }else{ u = w/2; v = w; } g.setColor(Color.CYAN); g.fillRect(0, 0, w, 2*h/3); g.setColor(Color.GREEN); g.fillRect(0, 2*h/3, w/2, h); g.setColor(Color.ORANGE); g.fillRect(w/2, 2*h/3, w, h); if(scoreSeq%2==0){ g.setColor(Color.WHITE); g.fillRect(u, 2*h/3, v, h); } g.setColor(Color.BLACK); g.drawString("SCORE: "+rightWins,100,100); g.drawString("SCORE: "+leftWins,w-150,100); if(wired){ g.setColor(Color.BLACK); for(int j=1;j charQ = new PriorityQueue(); charQ.add(ball_1); charQ.add(ball_2); charQ.add(ball_3); charQ.add(score_2); charQ.add(score_3); while(!charQ.isEmpty()){ Character ch = charQ.remove(); drawCharacter(g, ch); } animating = true; //set animating to true; } public void checkCollisions(Ball b1, Ball b2){ Point diff = new Point(b1.x-b2.x, b1.y-b2.y, b1.z-b2.z); double d = diff.norm(); if( d < (b1.radius + b2.radius) ){ Point v1 = b1.velocity; Point v2 = b2.velocity; // Between the comment bars is modified code from // http://www.plasmaphysics.org.uk/programs/coll3d_cpp.htm ////////////////////////////////////////////////////////////////// double distance =b1.radius+b2.radius; double x21=b2.x-b1.x; double y21=b2.y-b1.y; double z21=b2.z-b1.z; double vx21=v2.x-v1.x; double vy21=v2.y-v1.y; double vz21=v2.z-v1.z; // **** calculate relative distance and relative speed *** double v=Math.sqrt(vx21*vx21 +vy21*vy21 +vz21*vz21); // **** return if distance between balls smaller than sum of radii **** if (d>distance){ }else if (v==0){ }else{ // **** shift coordinate system so that ball 1 is at the origin *** b2.x=x21; b2.y=y21; b2.z=z21; // **** boost coordinate system so that ball 2 is resting *** v1.x=-vx21; v1.y=-vy21; v1.z=-vz21; // **** find the polar coordinates of the location of ball 2 *** double theta2=Math.acos(b2.z/d); double phi2 = 0; if (b2.x==0 && b2.y==0) { phi2=0; } else { phi2=Math.atan2(b2.y,b2.x); } double st=Math.sin(theta2); double ct=Math.cos(theta2); double sp=Math.sin(phi2); double cp=Math.cos(phi2); // **** express the velocity vector of ball 1 in a rotated coordinate // system where ball 2 lies on the z-axis ****** double v1xr=ct*cp*v1.x+ct*sp*v1.y-st*v1.z; double v1yr=cp*v1.y-sp*v1.x; double v1zr=st*cp*v1.x+st*sp*v1.y+ct*v1.z; double thetav=Math.acos(v1zr/v); double phiv = 0; if (v1xr==0 && v1yr==0) { phiv=0; } else { phiv=Math.atan2(v1yr,v1xr); } // **** calculate the normalized impact parameter *** double dr=d*Math.sin(thetav)/distance; // **** calculate impact angles if balls do collide *** double alpha=Math.asin(-dr); double beta=phiv; double sbeta=Math.sin(beta); double cbeta=Math.cos(beta); // **** calculate time to collision *** double t=(d*Math.cos(thetav) -distance*Math.sqrt(1-dr*dr))/v; // **** update positions and reverse the coordinate shift *** b2.x=b2.x+v2.x*t +b1.x; b2.y=b2.y+v2.y*t +b1.y; b2.z=b2.z+v2.z*t +b1.z; b1.x=(v1.x+v2.x)*t +b1.x; b1.y=(v1.y+v2.y)*t +b1.y; b1.z=(v1.z+v2.z)*t +b1.z; // *** update velocities *** double a=Math.tan(thetav+alpha); double dv2z=1*(v1zr+a*(cbeta*v1xr+sbeta*v1yr))/((1+a*a)*(1)); double v2zr=dv2z; double v2xr=a*cbeta*dv2z; double v2yr=a*sbeta*dv2z; v1zr=v1zr-v2zr; v1xr=v1xr-v2xr; v1yr=v1yr-v2yr; // **** rotate the velocity vectors back and add the initial velocity // vector of ball 2 to retrieve the original coordinate system **** v1.x=ct*cp*v1xr-sp*v1yr+st*cp*v1zr +v2.x; v1.y=ct*sp*v1xr+cp*v1yr+st*sp*v1zr +v2.y; v1.z=ct*v1zr-st*v1xr +v2.z; v2.x=ct*cp*v2xr-sp*v2yr+st*cp*v2zr +v2.x; v2.y=ct*sp*v2xr+cp*v2yr+st*sp*v2zr +v2.y; v2.z=ct*v2zr-st*v2xr +v2.z; } //////////////////////////////////////////////////////////////// } } /**update all the physics*/ public void update(double dt){ ball_1.applyVelocity(dt); ball_2.applyVelocity(dt); ball_3.applyVelocity(dt); checkCollisions(ball_1, ball_2); checkCollisions(ball_1, ball_3); checkCollisions(ball_2, ball_3); updateBall(ball_1, dt); updateBall(ball_2, dt); updateBall(ball_3, dt); if ((ball_1.velocity.norm() < 0.5 || volley) && (ball_1.y>=-1.1*ball_1.radius) ){ scoreSeq = 10; rightWon = ( ball_1.x<0 ); if(rightWon) leftWins++; else rightWins++; } } public void updateBall(Ball b, double dt){ if(b.y<-b.radius){ b.addToVelocity(dt*grav.x,dt*grav.y,dt*grav.z); }else{ b.y = -b.radius; b.multiplyToVelocity(1,-1,1); b.multiplyToVelocity(dampening,dampening,dampening); if(b.y>0) b.addToVelocity(dt*grav.x,dt*grav.y,dt*grav.z); } // System.out.println("p = <"+ball_1.x+","+ball_1.y+","+ball_1.z+">"); // System.out.println("v = <"+ball_1.velocity.x+","+ball_1.velocity.y+","+ball_1.velocity.z+">"); // System.out.println("dt = "+dt); } /**iterate over the surfaces in machine, drawing each one**/ public void drawCharacter(Graphics g, Character c){ // if (debug) System.out.println("Drawing machine..."); // if (debug) System.out.println("Updating machine..."); c.updateMatrices(); // if (debug) System.out.println("Machine has "+mach.s_list.size()+" surfaces to draw..."); for(Surface s: c.s_list) placeSurface(g, s); //go through Q or list of drawable faces } /**Method to draw a surface in the 3D space*/ public void placeSurface(Graphics g, Surface s){ //get matrix for surface Matrix4x4 mat = s.matrix; faceQ = new PriorityQueue(); //sort the faces based on proximity to camera for(int[] face: s.f_list){ Point[] f = new Point[face.length]; for(int j =0; j 0); if(visible){ //actually draw the polygon if(!facing) c = Color.BLACK; faceQ.add(new ZPoly(xpoints, ypoints, max_z/len, c, facing)); } } public boolean mouseUp(Event e, int x, int y) { damage = true; return true; } public boolean mouseDrag(Event e, int x, int y) { // mach.x = (((double) x)-w/2)/scale; // mach.y = (((double) y)-h/2)/scale; damage = true; return true; } public boolean keyUp(Event e, int key) { switch (key) { case 'a': ball_2.addToVelocity(-2,0,0); break; case 'd': ball_2.addToVelocity(2,0,0); break; case 'w': ball_2.addToVelocity(0,0,-2); break; case 's': ball_2.addToVelocity(0,0,2); break; case ' ': ball_2.addToVelocity(0,-3,0); ball_2.y -= 0.1; break; case Event.LEFT: case '4': ball_3.addToVelocity(-2,0,0); break; case Event.RIGHT: case '6': ball_3.addToVelocity(2,0,0); break; case Event.UP: case '8': ball_3.addToVelocity(0,0,-2); break; case Event.DOWN: case '5': case '2': ball_3.addToVelocity(0,0,2); break; case Event.ENTER: case '0': ball_3.addToVelocity(0,-3,0); ball_3.y -= 0.1; break; case 'r': case 'R': initVals(); break; case '+': case '=': res = res*1.5; if(res>0.5) res=0.5; initVals(); break; case '_': case '-': res = res/1.5; if(res<0.02) res=0.02 ; initVals(); break; case 'l': case 'L': label = !label; break; case 'e': case 'E': wired = !wired; break; case 'V': case 'v': volley = !volley; break; case 'g': grav = new Point(grav.x/2, grav.y/2, grav.z/2); break; case 'G': grav = new Point(grav.x*2, grav.y*2, grav.z*2); break; case 't': case 'T': trans = !trans; break; case Event.END: ball_1.velocity = new Point(0,0,0); ball_1.y = 0; break; case Event.PGDN: zoom += 5; break; case Event.PGUP: zoom -= 10; break; } damage = true; return true; } }