import java.awt.*; import java.awt.event.*; import java.applet.*; /* Animation Sutherland-Hodgeman polygon clipping algorithm */ public class PolyClipCanvas extends Canvas{ PolyClipApplet parent; Polygon p ; //polygon that is being created interactively Rectangle r ; //clip rectangle /* These are really internal to the clipPolygon method, but we make them instance variables so that paint method can show state of algorithm */ Polygon lastClipped ; //last clipped polygon Polygon clipped ; //p clipped so far Point thisp ; //current point in algorithm Point lastp ; //previous point in algorithm Edge currentEdge ; //current edge for clipping /* these keep track of things during interactive drawing */ boolean creating = false; //have we started the polygon yet? int startx, starty; //start of rubber line int endx, endy; //end of rubber line public PolyClipCanvas(PolyClipApplet parent) { this.parent = parent; this.addMouseListener(new MyAdapter()); this.addMouseMotionListener(new RubberLine()); clear(); } /* erase Polgon p */ public void clear() { p = new Polygon(); lastClipped = new Polygon(); clipped = new Polygon(); r = new Rectangle(100,100,100,150); thisp = new Point(); lastp = new Point(); currentEdge = null; repaint(); } /* Return polygon interactively created */ public Polygon getPolygon() { return p; } /* Return clip rectangle interactively created */ public Rectangle getClipRectangle() { return r; } //radius of circles used to mark points static final int RADIUS = 4; //Colour for filling original polygon - very light grey static final Color pColor = new Color(229,229,229); //Colour for filling polygon being clipped this time - light grey static final Color lastClippedColor = new Color(200,200,200); /*draw the current state of the algorithm */ public void paint(Graphics g) { g.setColor(Color.blue); g.drawRect(r.x,r.y,r.width,r.height); //the clip rectangle g.setColor(pColor); g.fillPolygon(p); //the original polygon g.setColor(lastClippedColor); g.fillPolygon(lastClipped); //polygon being currently clipped if (currentEdge != null){ g.setColor(Color.blue); currentEdge.draw(g); //current clip edge g.setColor(Color.red); if (currentEdge.inside(lastp)) { //previous vertex on polygon, solid if inside g.fillOval(lastp.x-RADIUS,lastp.y-RADIUS,2*RADIUS,2*RADIUS); } else { g.drawOval(lastp.x-RADIUS,lastp.y-RADIUS,2*RADIUS,2*RADIUS); } g.setColor(Color.green); if (currentEdge.inside(thisp)) { //current vertex on polygon, solid if inside g.fillOval(thisp.x-RADIUS,thisp.y-RADIUS,2*RADIUS,2*RADIUS); } else { g.drawOval(thisp.x-RADIUS,thisp.y-RADIUS,2*RADIUS,2*RADIUS); } g.setColor(Color.red); g.drawPolyline(clipped.xpoints,clipped.ypoints,clipped.npoints); //polygon clipped so far } } /* stop and display state of algorithm */ public void step(String message) { repaint(); parent.step(message); } /** method to help with interactive drawing of rectangle*/ Rectangle rectFromCorners(int startx, int starty,int endx, int endy) { int w = Math.abs(endx-startx); int h = Math.abs(endy-starty); int x = Math.min(endx,startx); int y = Math.min(endy,starty); return new Rectangle(x,y,w,h); } /** Implements a rubber band line that follows cursor while button is down */ class RubberLine extends MouseMotionAdapter { public void mouseDragged(MouseEvent e) { Graphics g = getGraphics(); g.setXORMode(Color.cyan); if (e.isMetaDown()){ //right mouse down - we're drawing a rectangle r = rectFromCorners(startx,starty,endx,endy); g.drawRect(r.x,r.y,r.width,r.height); } else { g.drawLine(startx,starty,endx,endy); } endx = e.getX(); endy = e.getY(); if (e.isMetaDown()){ r = rectFromCorners(startx,starty,endx,endy); g.drawRect(r.x,r.y,r.width,r.height); } else { g.drawLine(startx,starty,endx,endy); } } public void mouseMoved(MouseEvent e) { if (creating) { mouseDragged(e); } } } static final int EPSILON = 4; class MyAdapter extends MouseAdapter { public void mousePressed(MouseEvent e) { startx = e.getX(); starty = e.getY(); endx = startx; endy = starty; if (e.isMetaDown()){ //nothing else to do if creating rectangle } else { if (Math.abs(startx - p.xpoints[0]) < EPSILON && Math.abs(starty - p.ypoints[0]) < EPSILON){ //polygon closed creating = false; repaint(); } else { if (!creating) { creating = true; p = new Polygon(); } p.addPoint(startx, starty); } } } public void mouseReleased(MouseEvent e) { if (e.isMetaDown()){ r = rectFromCorners(startx,starty,endx,endy); repaint(); } } } /* clip p against r and return result */ Polygon clipPolygon(Polygon p, Rectangle r){ lastClipped = p; lastClipped = clipSide(lastClipped, new Edge(r.x,r.y,r.x,r.y+r.height)); step("Left edge done"); lastClipped = clipSide(lastClipped, new Edge(r.x,r.y+r.height,r.x+r.width,r.y+r.height)); step("Top Edge done"); lastClipped = clipSide(lastClipped, new Edge(r.x+r.width,r.y+r.height,r.x+r.width,r.y)); step("Right edge done"); lastClipped = clipSide(lastClipped, new Edge(r.x+r.width,r.y,r.x,r.y)); step("Bottom edge done"); return lastClipped; } /* clip p against Edge e and return result */ Polygon clipSide(Polygon p, Edge e){ Point intersect; if (p.npoints == 0){ return p; //nothing to do } currentEdge = e; clipped = new Polygon(); lastp = new Point(p.xpoints[p.npoints-1],p.ypoints[p.npoints-1]); for (int i = 0; i < p.npoints; i++){ thisp = new Point(p.xpoints[i],p.ypoints[i]); if (e.inside(thisp) && e.inside(lastp)){ clipped.addPoint(thisp.x,thisp.y); step("Inside to Inside"); } else if (!e.inside(thisp) && e.inside(lastp)){ intersect = e.intersect(thisp,lastp); clipped.addPoint(intersect.x,intersect.y); step("Inside to Outside"); } else if (!e.inside(thisp) && !e.inside(lastp)){ /*nothing */ step("Outside to Outside"); } else if (e.inside(thisp) && !e.inside(lastp)){ intersect = e.intersect(lastp,thisp); clipped.addPoint(intersect.x,intersect.y); clipped.addPoint(thisp.x,thisp.y); step("Outside to Inside"); } lastp = thisp; } currentEdge = null; //so that paint won't draw currentEdge now we've //left the loop return clipped; } }