package a1; import graphicslib3D.Point3D; import java.util.Vector; import javax.media.opengl.GL; import javax.media.opengl.GLAutoDrawable; /** Represents a bezier curve with four control points. A bezier curve object is able to draw itself in an OpenGL environment. @author Steven Kodani */ public class BezierCurve { private Point3D p1, p2, p3, p4, selected; private int recursionLevel; //CTRLSIZE is half the size of graphic for a control point private final double CTRLSIZE = .01; /** Creates a new bezier curve by initializing the four control points and setting the initial recursion level to 4. @param pp1 The first control point. @param pp2 The second control point. @param pp3 The third control point. @param pp4 The fourth control point. */ public BezierCurve(Point3D pp1, Point3D pp2, Point3D pp3, Point3D pp4) { p1 = pp1; p2 = pp2; p3 = pp3; p4 = pp4; recursionLevel = 4; } /** Draws the curve represented by this object using the given GLAutoDrawable. Also draws the curve's four control points and lines connecting them. @param drawable The GLAutoDrawable from the canvas which the curve will be drawn on. */ public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); //Draw lines connecting the control points in red gl.glColor3d(1.0, 0.0, 0.0); gl.glBegin(gl.GL_LINES); gl.glVertex2d(p1.getX(), p1.getY()); gl.glVertex2d(p2.getX(), p2.getY()); gl.glVertex2d(p2.getX(), p2.getY()); gl.glVertex2d(p3.getX(), p3.getY()); gl.glVertex2d(p3.getX(), p3.getY()); gl.glVertex2d(p4.getX(), p4.getY()); gl.glEnd(); //Draw the bezier curve represented by this object in green gl.glColor3d(0.0, 1.0, 0.0); Vector points = new Vector(); points.add(p1); points.add(p2); points.add(p3); points.add(p4); drawBezierCurve(points, 0, drawable); //Draw the control points as blue squares gl.glColor3d(0.0, 0.0, 1.0); gl.glBegin(gl.GL_POLYGON); gl.glVertex2d(p1.getX() - CTRLSIZE, p1.getY() - CTRLSIZE); gl.glVertex2d(p1.getX() + CTRLSIZE, p1.getY() - CTRLSIZE); gl.glVertex2d(p1.getX() + CTRLSIZE, p1.getY() + CTRLSIZE); gl.glVertex2d(p1.getX() - CTRLSIZE, p1.getY() + CTRLSIZE); gl.glEnd(); gl.glBegin(gl.GL_POLYGON); gl.glVertex2d(p2.getX() - CTRLSIZE, p2.getY() - CTRLSIZE); gl.glVertex2d(p2.getX() + CTRLSIZE, p2.getY() - CTRLSIZE); gl.glVertex2d(p2.getX() + CTRLSIZE, p2.getY() + CTRLSIZE); gl.glVertex2d(p2.getX() - CTRLSIZE, p2.getY() + CTRLSIZE); gl.glEnd(); gl.glBegin(gl.GL_POLYGON); gl.glVertex2d(p3.getX() - CTRLSIZE, p3.getY() - CTRLSIZE); gl.glVertex2d(p3.getX() + CTRLSIZE, p3.getY() - CTRLSIZE); gl.glVertex2d(p3.getX() + CTRLSIZE, p3.getY() + CTRLSIZE); gl.glVertex2d(p3.getX() - CTRLSIZE, p3.getY() + CTRLSIZE); gl.glEnd(); gl.glBegin(gl.GL_POLYGON); gl.glVertex2d(p4.getX() - CTRLSIZE, p4.getY() - CTRLSIZE); gl.glVertex2d(p4.getX() + CTRLSIZE, p4.getY() - CTRLSIZE); gl.glVertex2d(p4.getX() + CTRLSIZE, p4.getY() + CTRLSIZE); gl.glVertex2d(p4.getX() - CTRLSIZE, p4.getY() + CTRLSIZE); gl.glEnd(); } /** Draws this bezier curve recursively using a canvas's GLAutoDrawable using the given control points. @param points The control points for the Bezier curve. @param level Allows the recursion to track the number of subdivisions in the curve rendering process. @param drawable The GLAutoDrawable from the canvas which the curve will be drawn on. */ private void drawBezierCurve(Vector points, int level, GLAutoDrawable drawable) { //If an ending condition is met, draw a strait line if (straightEnough(points.get(0), points.get(1), points.get(2), points.get(3)) || level > recursionLevel) { GL gl = drawable.getGL(); gl.glBegin(GL.GL_LINES); gl.glVertex2d(points.get(0).getX(), points.get(0).getY()); gl.glVertex2d(points.get(3).getX(), points.get(3).getY()); gl.glEnd(); } else { //Subdivide the current control points and recurse Vector leftCurve, rightCurve; leftCurve = new Vector(); rightCurve = new Vector(); subdivideCurve(points, rightCurve, leftCurve); drawBezierCurve(leftCurve, level + 1, drawable); drawBezierCurve(rightCurve, level + 1, drawable); } } /** Used for drawing bezier curves. Determines whether or not a segment of the curve is strait enough or if it needs to be subdivided further. @param p1 The first control point. @param p2 The second control point. @param p3 The third control point. @param p4 The fourth control point. @return Whether or not the curve should be subdivided further. */ private boolean straightEnough(Point3D p1, Point3D p2, Point3D p3, Point3D p4) { //Compute the difference between the sum of the distances between //each of the control points and the difference between the first and //last control point double d1 = p1.distanceTo(p2) + p2.distanceTo(p3) + p3.distanceTo(p4); double d2 = p1.distanceTo(p4); //If they are close enough, return true - otherwise return false if (Math.abs(d1 - d2) < .0001) return true; else return false; } /** Used for drawing bezier curves. Subdivides a set of control points into two sets of control points, each of which represent a bezier curve. @param q The vector containing the origional control points. @param r The vector which will be populated with the control points for the right half of the curve. @param l The vector which will be populated with the control points for the left half of the curve. */ private void subdivideCurve(Vector q, Vector r, Vector l) { //Calculate the new control points l.setSize(4); r.add(q.get(0)); r.add(new Point3D( (q.get(0).getX() + q.get(1).getX()) / 2, (q.get(0).getY() + q.get(1).getY()) / 2, 0.0)); r.add(new Point3D( r.get(1).getX() / 2 + (q.get(1).getX() + q.get(2).getX()) / 4, r.get(1).getY() / 2 + (q.get(1).getY() + q.get(2).getY()) / 4, 0.0)); l.setElementAt(q.get(3), 3); l.setElementAt(new Point3D( (q.get(2).getX() + q.get(3).getX()) / 2, (q.get(2).getY() + q.get(3).getY()) / 2, 0.0), 2); l.setElementAt(new Point3D( (q.get(1).getX() + q.get(2).getX()) / 4 + l.get(2).getX() / 2, (q.get(1).getY() + q.get(2).getY()) / 4 + l.get(2).getY() / 2, 0.0), 1); r.add(new Point3D( (r.get(2).getX() + l.get(1).getX()) / 2, (r.get(2).getY() + l.get(1).getY()) / 2, 0.0)); l.setElementAt(r.get(3), 0); } /** Increases the level of recusion used for drawing the curve by one. */ public void increaseRecursion() { recursionLevel++; } /** Decreases the level of recursion used for drawing the curve by one. */ public void decreaseRecursion() { //Check to verify that we still have recursion levels to decrease //The recursion level must be a non-zero positive number if (recursionLevel > 1) recursionLevel--; } /** Checks a set of x, y coordinates against all of the control points and if it is within the graphical area of a control point, then that control point will be selected. It is intended that the x, y coordinate passed into this method is the location of a mouse click. @param x The x position of the selection. @param y The y position of the selection. */ public void selectControlPoint(double x, double y) { //Check each control point if the given point is contained within it if (x > p1.getX() - CTRLSIZE && x < p1.getX() + CTRLSIZE && y > p1.getY() - CTRLSIZE && y < p1.getY() + CTRLSIZE) selected = p1; else if (x > p2.getX() - CTRLSIZE && x < p2.getX() + CTRLSIZE && y > p2.getY() - CTRLSIZE && y < p2.getY() + CTRLSIZE) selected = p2; else if (x > p3.getX() - CTRLSIZE && x < p3.getX() + CTRLSIZE && y > p3.getY() - CTRLSIZE && y < p3.getY() + CTRLSIZE) selected = p3; else if (x > p4.getX() - CTRLSIZE && x < p4.getX() + CTRLSIZE && y > p4.getY() - CTRLSIZE && y < p4.getY() + CTRLSIZE) selected = p4; } /** Deselects the currently selected control point if one is selected. */ public void deselectControlPoint() { selected = null; } /** If a control point is selected, this method will move that control point to the new location specified by the given x, y coordinate. @param x The x position of where the control point will be moved to. @param y The y position of where the control point will be moved to. */ public void moveControlPoint(double x, double y) { //If a control point is selected, move it to the new x,y coordinates if (selected != null) { selected.setX(x); selected.setY(y); } } }