// Import packages import java.awt.Graphics; import java.awt.Color; import java.awt.Event; import java.awt.Image; import java.awt.Font; import java.awt.FontMetrics; // applet class /* This part of the declarations contains the class that defines the control point objects. These control points contain the x, y, and z values necessary to compute the graph using the functions for Bezier surfaces. */ class ControlPoint extends Object { public int x; public int y; public double z; public static final int PT_SIZE = 4; public ControlPoint(int a, int b) { x = a; y = b; } public void SetZ(double a) { z = a; } public boolean within(int a, int b) { if (a >= x - PT_SIZE && b >= y - PT_SIZE && a <= x + PT_SIZE && b <= y + PT_SIZE) return true; else return false; } } public class BezSurf extends java.applet.Applet { int dh, dv,dhx2,dvx2,xinc,yinc; int moving_point; int selected_point; Image buf; Graphics gbuf; // vars for 3d -> 2d transform double xform[] = new double[11]; int hv[] = new int[2]; double alt = 30, az = 20, mag = 30; double mag0 = 30; int mx0 = 0, my0 = 0; Font f = new Font("TimesRoman", Font.PLAIN, 10); Font f1 = new Font("TimesRoman", Font.PLAIN, 12); // arrays for surface coords // 3d double surfx[] = new double[10 * 10]; double surfy[] = new double[10 * 10]; double surfz[] = new double[10 * 10]; ControlPoint points[] = new ControlPoint[25]; // 2d int surfh[] = new int[10 * 10]; int surfv[] = new int[10 * 10]; double xp[] = new double[25]; double yp[] = new double[25]; double zp[] = new double[25]; // slider params double zval = 0, zoom = 30; // slider data type /* slider[0] = min slider[1] = max slider[2] = val slider[3] = h slider[4] = v slider[5] = hit slider[6] = dv0 slider[7] = name index */ // slider vars String snames[] = new String[3]; double slider1[] = new double[8]; double slider2[] = new double[8]; /* these functions are used to convert the control points pixel position to its real number value for use in the Bezier surface equation and vice-versa */ public int real2pixelx(double realx) { int pixel; pixel = (int)(realx*xinc + 5*xinc); return pixel; } public double pixel2realx(int pixel) { double realx; realx = (double)((pixel - 5*xinc)/xinc); return realx; } public int real2pixely(double realy) { int pixel; pixel = (int)(-(realy*yinc) + 5*yinc); return pixel; } public double pixel2realy(int pixel) { double realy; realy = (double)((-pixel + 5*yinc)/yinc); return realy; } /* init() sets up the applet the first time through and is called whenever the reset button is hit in order to put the applet back at the starting point. */ public void init() { dhx2 = this.size().width; dv = this.size().height; dh = dhx2/2; xinc = dh/12; yinc = dv/12; zoom = 30; mag = zoom; zval = 0; points[0] = new ControlPoint(real2pixelx(-2.0),real2pixely(2.0)); points[1] = new ControlPoint(real2pixelx(-1.0),real2pixely(2.0)); points[2] = new ControlPoint(real2pixelx( 0.0),real2pixely(2.0)); points[3] = new ControlPoint(real2pixelx( 1.0),real2pixely(2.0)); points[4] = new ControlPoint(real2pixelx( 2.0),real2pixely(2.0)); points[5] = new ControlPoint(real2pixelx(-2.0),real2pixely(1.0)); points[6] = new ControlPoint(real2pixelx(-1.0),real2pixely(1.0)); points[7] = new ControlPoint(real2pixelx( 0.0),real2pixely(1.0)); points[8] = new ControlPoint(real2pixelx( 1.0),real2pixely(1.0)); points[9] = new ControlPoint(real2pixelx( 2.0),real2pixely(1.0)); points[10] = new ControlPoint(real2pixelx(-2.0),real2pixely(0.0)); points[11] = new ControlPoint(real2pixelx(-1.0),real2pixely(0.0)); points[12] = new ControlPoint(real2pixelx( 0.0),real2pixely(0.0)); points[13] = new ControlPoint(real2pixelx( 1.0),real2pixely(0.0)); points[14] = new ControlPoint(real2pixelx( 2.0),real2pixely(0.0)); points[15] = new ControlPoint(real2pixelx(-2.0),real2pixely(-1.0)); points[16] = new ControlPoint(real2pixelx(-1.0),real2pixely(-1.0)); points[17] = new ControlPoint(real2pixelx( 0.0),real2pixely(-1.0)); points[18] = new ControlPoint(real2pixelx( 1.0),real2pixely(-1.0)); points[19] = new ControlPoint(real2pixelx( 2.0),real2pixely(-1.0)); points[20] = new ControlPoint(real2pixelx(-2.0),real2pixely(-2.0)); points[21] = new ControlPoint(real2pixelx(-1.0),real2pixely(-2.0)); points[22] = new ControlPoint(real2pixelx( 0.0),real2pixely(-2.0)); points[23] = new ControlPoint(real2pixelx( 1.0),real2pixely(-2.0)); points[24] = new ControlPoint(real2pixelx( 2.0),real2pixely(-2.0)); // create offscreen drawing buf = createImage(dhx2, dv); gbuf = buf.getGraphics(); // set up initial 3d coordinates, surface, and 2d representation reset_coords(0.0, 0.0, 0.0, alt, az, mag, xform); calc_surf(); project_surf(); // initialize sliders (not currently a separate class) snames[0] = "Zoom"; snames[1] = "Z value"; slider1[0] = 0; slider1[1] = 60; slider1[2] = zoom; slider1[3] = dh - 20; slider1[4] = 19; slider1[5] = 0; slider1[6] = 0; slider1[7] = 0; slider2[0] = -5; slider2[1] = 5; slider2[2] = zval; slider2[3] = 2*dh - 20; slider2[4] = 19; slider2[5] = 0; slider2[6] = 0; slider2[7] = 1; } private int findPoint(int a, int b) { // Scan the list of control points to find out which (if any) point // contains the coordinates: a,b. // If a point is found, return the point's index, otherwise return -1 int max = 24; for(int i = 0; i <= max; i++) { ControlPoint pnt = points[i]; if (pnt.within(a,b)) { return i; } } return -1; } /* calc 3d surface values from control point values. We found it faster to hard code the Bezier blending function for 25 points here rather than to use an iterative method. It is ugly however it is more efficient. */ public void calc_surf() { int count = 0; for (int q =0; q<=24;q++) { xp[q] = pixel2realx(points[q].x); yp[q] = pixel2realy(points[q].y); zp[q] = points[q].z; } for (double u = 0; u <= 1; u += 0.1111111111) for (double v = 0; v <= 1; v += 0.1111111111) { count++; surfx[count-1] = xp[0]*Math.pow(1 - u, 4)*Math.pow(1 - v, 4) + xp[1]*4*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 4)+ xp[2]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 4) + xp[3]*4*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 4)+ xp[4]*Math.pow(u, 4)*Math.pow(1 - v, 4)+ xp[5]*4*Math.pow(1 - u, 4)*Math.pow(1 - v, 3)*v+ xp[6]*16*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 3)*v+ xp[7]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 3)*v+ xp[8]*16*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 3)*v+ xp[9]*4*Math.pow(u, 4)*Math.pow(1 - v, 3)*v + xp[10]*6*Math.pow(1-u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ xp[11]*24*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 2)*Math.pow(v, 2)+ xp[12]*36*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ xp[13]*24*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ xp[14]*6*Math.pow(u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ xp[15]*4*Math.pow(1-u, 4)*(1-v)*Math.pow(v, 3)+ xp[16]*16*Math.pow(1 - u, 3)*u*(1-v)*Math.pow(v, 3)+ xp[17]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*(1-v)*Math.pow(v, 3)+ xp[18]*16*Math.pow(u, 3)*(1-u)*(1-v)*Math.pow(v, 3)+ xp[19]*4*Math.pow(u, 4)*(1-v)*Math.pow(v, 3)+ xp[20]*Math.pow(1-u, 4)*Math.pow(v, 4)+ xp[21]*4*Math.pow(1-u, 3)*u*Math.pow(v, 4)+ xp[22]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(v, 4)+ xp[23]*4*Math.pow(u, 3)*(1-u)*Math.pow(v, 4)+ xp[24]*Math.pow(u, 4)*Math.pow(v, 4); surfy[count-1] = yp[0]*Math.pow(1 - u, 4)*Math.pow(1 - v, 4) + yp[1]*4*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 4)+ yp[2]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 4) + yp[3]*4*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 4)+ yp[4]*Math.pow(u, 4)*Math.pow(1 - v, 4)+ yp[5]*4*Math.pow(1 - u, 4)*Math.pow(1 - v, 3)*v+ yp[6]*16*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 3)*v+ yp[7]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 3)*v+ yp[8]*16*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 3)*v+ yp[9]*4*Math.pow(u, 4)*Math.pow(1 - v, 3)*v + yp[10]*6*Math.pow(1-u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ yp[11]*24*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 2)*Math.pow(v, 2)+ yp[12]*36*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ yp[13]*24*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ yp[14]*6*Math.pow(u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ yp[15]*4*Math.pow(1-u, 4)*(1-v)*Math.pow(v, 3)+ yp[16]*16*Math.pow(1 - u, 3)*u*(1-v)*Math.pow(v, 3)+ yp[17]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*(1-v)*Math.pow(v, 3)+ yp[18]*16*Math.pow(u, 3)*(1-u)*(1-v)*Math.pow(v, 3)+ yp[19]*4*Math.pow(u, 4)*(1-v)*Math.pow(v, 3)+ yp[20]*Math.pow(1-u, 4)*Math.pow(v, 4)+ yp[21]*4*Math.pow(1-u, 3)*u*Math.pow(v, 4)+ yp[22]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(v, 4)+ yp[23]*4*Math.pow(u, 3)*(1-u)*Math.pow(v, 4)+ yp[24]*Math.pow(u, 4)*Math.pow(v, 4); surfz[count-1] = zp[0]*Math.pow(1 - u, 4)*Math.pow(1 - v, 4) + zp[1]*4*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 4)+ zp[2]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 4) + zp[3]*4*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 4)+ zp[4]*Math.pow(u, 4)*Math.pow(1 - v, 4)+ zp[5]*4*Math.pow(1 - u, 4)*Math.pow(1 - v, 3)*v+ zp[6]*16*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 3)*v+ zp[7]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 3)*v+ zp[8]*16*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 3)*v+ zp[9]*4*Math.pow(u, 4)*Math.pow(1 - v, 3)*v + zp[10]*6*Math.pow(1-u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ zp[11]*24*Math.pow(1 - u, 3)*u*Math.pow(1 - v, 2)*Math.pow(v, 2)+ zp[12]*36*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ zp[13]*24*Math.pow(u, 3)*(1-u)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ zp[14]*6*Math.pow(u, 4)*Math.pow(1 - v, 2)*Math.pow(v, 2)+ zp[15]*4*Math.pow(1-u, 4)*(1-v)*Math.pow(v, 3)+ zp[16]*16*Math.pow(1 - u, 3)*u*(1-v)*Math.pow(v, 3)+ zp[17]*24*Math.pow(1 - u, 2)*Math.pow(u, 2)*(1-v)*Math.pow(v, 3)+ zp[18]*16*Math.pow(u, 3)*(1-u)*(1-v)*Math.pow(v, 3)+ zp[19]*4*Math.pow(u, 4)*(1-v)*Math.pow(v, 3)+ zp[20]*Math.pow(1-u, 4)*Math.pow(v, 4)+ zp[21]*4*Math.pow(1-u, 3)*u*Math.pow(v, 4)+ zp[22]*6*Math.pow(1 - u, 2)*Math.pow(u, 2)*Math.pow(v, 4)+ zp[23]*4*Math.pow(u, 3)*(1-u)*Math.pow(v, 4)+ zp[24]*Math.pow(u, 4)*Math.pow(v, 4); } } // handle mouse click // a function to test if they click inside the control point square. public boolean InsideSquare(int x,int y) { if ((x>dh+xinc)&&(xyinc)&&(y<11*yinc)) return true; else return false; } // a function to test if they click the reset button. public boolean InReset(int x, int y) { if ((x>dh+5*xinc)&&(y>11*yinc+10)&&(xdh) if (InsideSquare(x,y)) { moving_point = findPoint(x-dh-xinc, y-yinc); selected_point = moving_point; slider2[2] = points[selected_point].z; } else slider_hit(slider2, x, y); else // save current mouse pos and magnification { mx0 = x; my0 = y; mag0 = mag; // check for click in sliders slider_hit(slider1, x, y); } return true; } /* handle dragging (rotate or zoom or sliders or drag control points) note that surface computation is synchronized with drawing. Also, if they drag a point, then we will re - compute the surface after they are finished draging the point. */ public synchronized boolean mouseDrag(Event evt, int x, int y) { // track mouse in slider and update surface if (x>dh) if (InsideSquare(x,y)&&(moving_point >=0)) { ControlPoint pnt = points[moving_point]; pnt.x = x-dh-xinc; pnt.y = y-yinc; } // this slider handles the z value of the points else if (track_slider(slider2, x, y)) { zval = slider2[2]; points[selected_point].z=zval; } else ; // this slider handles the zooming. else if (track_slider(slider1, x, y)) { zoom = slider1[2]; mag =zoom; reset_coords(0.0, 0.0, 0.0, alt, az, mag, xform); project_surf(); } else //do rotation { // change altitude & azimuth alt += y - my0; az += x - mx0; my0 = y; mx0 = x; // update coords, surface, and redraw reset_coords(0.0, 0.0, 0.0, alt, az, mag, xform); project_surf(); } repaint(); return true; } /* Once the mouse comes up off of a control point or the slider to set the z value, we recalculate and redisplay the surface. We don't do this while they are dragging the point because it would be much too slow. */ public boolean mouseUp(Event evt, int x,int y) { if ((moving_point >=0)||(selected_point >= 0)) { moving_point = -1; calc_surf(); project_surf(); repaint(); } return true; } // transform 3d surface to 2d public void project_surf() { for (int i = 0; i < 10; i ++) for (int j = 0; j < 10; j ++) { int k = i + j * 10; xform_3to2d(surfx[k], surfy[k], surfz[k], xform, hv); surfh[k] = hv[0]; surfv[k] = hv[1]; } } // draw the second frame containing the control points. public void draw_frame2(Graphics g) { g.setFont(f1); g.setColor(Color.white); g.fillRect(dh,0,dh,dv); g.setColor(Color.black); g.drawRect(dh+xinc,yinc,10*xinc,10*yinc); g.drawRect(dh+5*xinc,11*yinc+10,2*xinc,yinc/2); g.drawLine(dh+dh/2,yinc,dh+dh/2,dv-yinc-5); g.drawLine(dh+xinc,dv/2,dhx2-xinc-5,dv/2); g.drawString("Reset",dh+5*xinc+17,11*yinc+22); g.drawString("drag points on the x,y plane - use slider to set z",dh+2*xinc+3,25); g.setFont(f); } //This draws the first frame which contains the surface. public void draw_frame(Graphics g) { g.setColor(Color.black); g.fillRect(0, 0, dh, dv); g.setFont(f); // transform 3d axis positions to 2d xform_3to2d(0.0, 0.0, 0.0, xform, hv); int h0 = hv[0]; int v0 = hv[1]; xform_3to2d(1.0, 0.0, 0.0, xform, hv); int h1 = hv[0]; int v1 = hv[1]; xform_3to2d(0.0, 1.0, 0.0, xform, hv); int h2 = hv[0]; int v2 = hv[1]; xform_3to2d(0.0, 0.0, 1.0, xform, hv); int h3 = hv[0]; int v3 = hv[1]; // draw axes g.setColor(Color.yellow); g.drawLine(h0, v0, h1, v1); g.drawLine(h0, v0, h2, v2); g.drawLine(h0, v0, h3, v3); // draw axis labels xform_3to2d(1.1, 0.0, 0.0, xform, hv); g.drawString("X", hv[0], hv[1]); xform_3to2d(0.0, 1.1, 0.0, xform, hv); g.drawString("Y", hv[0], hv[1]); xform_3to2d(0.0, 0.0, 1.1, xform, hv); g.drawString("Z", hv[0], hv[1]); g.drawString("Drag to rotate", 2, dv - 5); FontMetrics fm = getFontMetrics(f); } //draw control points public void draw_points(Graphics g) { g.setColor(Color.red); for (int i=0; i <= 24; i++) { if (i==selected_point) g.setColor(Color.blue); else g.setColor(Color.red); ControlPoint p = points[i]; g.drawRect(p.x-p.PT_SIZE+dh+xinc, p.y-p.PT_SIZE+yinc, p.PT_SIZE*2, p.PT_SIZE*2); g.drawString(String.valueOf(i),p.x+p.PT_SIZE+dh+xinc,p.y-p.PT_SIZE+yinc); } } // draw surface public void draw_surf(Graphics g) { g.setColor(Color.green); // draw one set of lines for (int i = 0; i < 10; i ++) for (int j = 1; j < 10; j ++) { int k1 = i + (j - 1) * 10; int k2 = i + j * 10; g.drawLine(surfh[k1], surfv[k1], surfh[k2], surfv[k2]); } // draw other set of lines for (int j = 0; j < 10; j ++) for (int i = 1; i < 10; i ++) { int k1 = i - 1 + j * 10; int k2 = i + j * 10; g.drawLine(surfh[k1], surfv[k1], surfh[k2], surfv[k2]); } } // do drawing // note that drawing is synchronized with surface computation public synchronized void paint(Graphics g) { draw_frame(gbuf); draw_surf(gbuf); draw_frame2(gbuf); draw_points(gbuf); draw_slider(slider1, gbuf); draw_slider(slider2, gbuf); // copy to screen g.drawImage(buf, 0, 0, this); //draw_points(g); } // overload for no flicker public void update(Graphics g) { paint(g); } // coord transform data structure /* xform[0] = x0 xform[1] = y0 xform[2] = z0 xform[3] = mag xform[4] = h0 xform[5] = v0 xform[6] = cxh xform[7] = cyh xform[8] = cxv xform[9] = cyv xform[10] = czv */ // update transform based on current alt, az, mag public void reset_coords(double x0, double y0, double z0, double alt, double az, double mag, double xform[]) { xform[0] = x0; xform[1] = y0; xform[2] = z0; xform[3] = mag; xform[4] = dh / 2.0; xform[5] = dv / 2.0; double cos_alt = Math.cos(alt * Math.PI / 180.0); double sin_alt = Math.sin(alt * Math.PI / 180.0); double cos_az = Math.cos(az * Math.PI / 180.0); double sin_az = Math.sin(az * Math.PI / 180.0); xform[6] = sin_az; xform[7] = cos_az; xform[8] = -sin_alt * cos_az; xform[9] = sin_alt * sin_az; xform[10] = cos_alt; } // transform 3d to 2d by matrix multiplication public void xform_3to2d(double x, double y, double z, double xform[], int hv[]) { x = (x - xform[0]) * xform[3]; y = (y - xform[1]) * xform[3]; z = (z - xform[2]) * xform[3]; hv[0] = (int) (xform[4] + xform[6] * x + xform[7] * y); hv[1] = (int) (xform[5] - xform[8] * x - xform[9] * y - xform[10] * z); } // draw a slider public void draw_slider(double slider[], Graphics g) { if (slider[7] == 0) g.setColor(Color.yellow); else g.setColor(Color.blue); int h1 = (int) slider[3]; int v1 = (int) slider[4]; int v2 = v1 + 60; g.drawLine(h1, v1, h1, v2); // get current thumb pos and draw int v3; if (slider[0] == slider[1]) v3 = (v1 + v2) / 2; else v3 = (int) (v2 - (v2 - v1) * (slider[2] - slider[0]) / (slider[1] - slider[0])); g.fillRect(h1 - 4, v3 - 4, 10, 10); g.setFont(f); FontMetrics fm = getFontMetrics(f); String s = String.valueOf(((int) (slider[2] * 100)) / 100.0); int w = fm.stringWidth(s); g.drawString(s, h1 - w / 2, v2 + 15); String s2 = snames[(int) slider[7]]; int w2 = fm.stringWidth(s2); g.drawString(s2, h1 - w2 / 2, v1 - 7); } // check to see if mouse click is in slider public void slider_hit(double slider[], int x, int y) { slider[5] = 0; slider[6] = 0; int h1 = (int) slider[3]; int v1 = (int) slider[4]; int v2 = v1 + 60; int v3; if (slider[0] == slider[1]) v3 = (v1 + v2) / 2; else v3 = (int) (v2 - (v2 - v1) * (slider[2] - slider[0]) / (slider[1] - slider[0])); if (x < h1 - 4) return; if (x > h1 + 4) return; if (y < v3 - 4) return; if (y > v3 + 4) return; slider[5] = 1; slider[6] = y - v3; } // track mouse dragging in slider public boolean track_slider(double slider[], int x, int y) { // see if slider is "hit" if (slider[5] != 1) return false; int h1 = (int) slider[3]; int v1 = (int) slider[4]; int v2 = v1 + 60; y -= slider[6]; if (y < v1) y = v1; if (y > v2) y = v2; // update slider value double val; if (slider[0] == slider[1]) val = slider[0]; else val = slider[0] + ((v2 - y) * (slider[1] - slider[0])) / (v2 - v1); if (val < slider[0]) val = slider[0]; if (val > slider[1]) val = slider[1]; slider[2] = val; return true; } }