import java.awt.*; import javax.swing.*; import java.util.*; import java.awt.geom.*; /** * This class plots a sine curve between -2*PI and 2*PI. */ public class cardioid { // whether or not to draw a connecting line between the dots static boolean drawConnectingLine = true; public static void main (String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Sinusoid s = new Sinusoid(); s.setPreferredSize(new Dimension(800, 600)); s.setBackground(Color.white); frame.getContentPane().add(s); frame.pack(); frame.setVisible(true); } }); } static class Sinusoid extends JPanel { int previousY = 0; double degToRad (int deg) { return ((2*Math.PI)/360.0) * deg; } int scale (int i, int width) { return (int) ((i/(double)width)*720.0); } int plotPointsSize; double[] eval; ArrayList plotPoints = new ArrayList(); Sinusoid() { int MAX_PIXEL_DISTANCE = 10; // pixels double MIN_PIXEL_DISTANCE = 0.5; // pixels // maximum angle between two line segments double MAX_ANGLE = 10; // degrees double MAX_ANGLE_OFF_SCREEN = 45; // degrees double MAX_BEND = Math.tan(MAX_ANGLE * Math.PI/180.0); // maximum number of bisections (max number of plot points = 2^MAX_DEPTH) int MAX_DEFINED_BISECTIONS = 16; int MAX_PROBLEM_BISECTIONS = 8; // the curve is sampled at least at this many positions to plot it int MIN_SAMPLE_POINTS = 180; eval = new double[2]; int LENGTH = MAX_DEFINED_BISECTIONS + 1; double[] divisors = new double[LENGTH]; double x0,y0,t,x,y; int countEvaluations; //ArrayList plotPoints = new ArrayList(); double t1 = 0; double t2 = 2*Math.PI; double max_param_step = Math.abs(t2-t1) / MIN_SAMPLE_POINTS; // evaluate for t1 evaluateCurve(t1); x0=eval[0]; // xEval(t1); y0=eval[1]; // yEval(t1); // evaluate for t2 evaluateCurve(t2); x = eval[0]; // xEval(t2); y = eval[1]; // yEval(t2); // FIRST POINT // c(t1) and c(t2) are defined, lets go ahead and move to our first point (x0, y0) // note: lineTo will automatically do a moveTo if this is the first gp point plotPoints.add(x0); plotPoints.add(y0); // INIT plotting algorithm //int LENGTH = MAX_DEFINED_BISECTIONS + 1; int dyadicStack[] = new int[LENGTH]; int depthStack[] = new int[LENGTH]; double xStack[] = new double[LENGTH]; double yStack[] = new double[LENGTH]; //double divisors[] = new double[LENGTH]; divisors[0]=t2-t1; for (int i=1; i < LENGTH; divisors[i] = divisors[i-1]/2, i++); int i=1; dyadicStack[0]=1; depthStack[0]=0; xStack[0] = x; // xEval(t2); yStack[0] = y; // yEval(t2); // slope between (t1, t2) double ydiff = y - y0; double xdiff = x - x0; // init previous slope using (t1, t1 + min_step) evaluateCurve(t1 + divisors[LENGTH-1]); double prevXdiff = eval[0] - x0; double prevYdiff = eval[1] - y0; int top=1; int depth=0; t = t1; double left = t1; boolean distanceOK, angleOK; // Actual plotting algorithm: // use bisection for interval until we reach // a small pixel distance between two points and // a small angle between two segments. // The evaluated curve points are stored on a stack // to avoid multiple evaluations at the same position. do { // pixel distance from last point OK? distanceOK = isDistanceOK(xdiff, ydiff); // angle from last segment OK? angleOK = isAngleOK(prevXdiff, prevYdiff, xdiff, ydiff, MAX_BEND); // bisect interval as long as ... while ( // max bisection depth not reached depth < MAX_DEFINED_BISECTIONS && // distance not ok or angle not ok or step too big (!distanceOK || !angleOK || divisors[depth] > max_param_step) ) { // push stacks dyadicStack[top] = i; depthStack[top] = depth; xStack[top] = x; yStack[top] = y; i = 2*i - 1; top++; depth++; // println("evaluations: " + countEvaluations + " t: " + t); t = t1 + i * divisors[depth]; // t=t1+(t2-t1)*(i/2^depth) // evaluate curve for parameter t evaluateCurve(t); // check for singularity: // c(t) undefined; c(t-eps) and c(t+eps) both defined x = eval[0]; // xEval(t); y = eval[1]; // yEval(t); xdiff = x - x0; ydiff = y - y0; // pixel distance from last point OK? distanceOK = isDistanceOK(xdiff, ydiff); // angle from last segment OK? angleOK = isAngleOK(prevXdiff, prevYdiff, xdiff, ydiff, MAX_BEND); } // end of while-loop for interval bisections // add point to general path: lineTo or moveTo? plotPoints.add(x); plotPoints.add(y); // remember last point in general path x0=x; y0=y; left = t; /* * Here's the real utility of the algorithm: Now pop stack and go to * right; notice the corresponding dyadic value when we go to right is * 2*i/(2^(d+1) = i/2^d !! So we've already calculated the corresponding * x and y values when we pushed. */ --top; y = yStack[top]; x = xStack[top]; depth = depthStack[top]+1; // pop stack and go to right i = dyadicStack[top] * 2; prevXdiff = xdiff; prevYdiff = ydiff; xdiff = x - x0; ydiff = y - y0; t = t1 + i * divisors[depth]; } while (top !=0); // end of do-while loop for bisection stack plotPointsSize = plotPoints.size(); } // end of Sinusoid constructor boolean isDistanceOK(double xdiff, double ydiff) { return // distance from last point too large (Math.abs(xdiff) <= 10 && Math.abs(ydiff) <= 10); } /** * Returns true when x is either NaN or infinite. */ boolean isUndefined(double x) { return Double.isNaN(x) || Double.isInfinite(x); } /** * Returns true when at least one element of eval is either NaN or infinite. */ boolean isUndefined(double [] eval) { for (int i=0; i < eval.length; i++) { if (isUndefined(eval[i])) return true; } return false; } /** * Returns whether the angle between the vectors (vx, vy) and (wx, wy) * is smaller than MAX_BEND, where MAX_BEND = tan(MAX_ANGLE). */ boolean isAngleOK(double vx, double vy, double wx, double wy, double MAX_BEND) { // |v| * |w| * sin(alpha) = |det(v, w)| // cos(alpha) = v . w / (|v| * |w|) // tan(alpha) = sin(alpha) / cos(alpha) // tan(alpha) = |det(v, w)| / v . w // small angle: tan(alpha) < MAX_BEND // |det(v, w)| / v . w < MAX_BEND // |det(v, w)| < MAX_BEND * (v . w) double innerProduct = vx * wx + vy * wy; if (isUndefined(innerProduct)) { return true; } else if (innerProduct <= 0) { // angle >= 90 degrees return false; } else { // angle < 90 degrees // small angle: |det(v, w)| < MAX_BEND * (v . w) double det = Math.abs(vx * wy - vy * wx); return det < MAX_BEND * innerProduct; } } void evaluateCurve(double tvalue) { // sideways parabola // eval[0] = Math.pow(tvalue,2)+2.0; // eval[1] = tvalue; // standard normal curve // eval[0] = tvalue; // eval[1] = 1/Math.sqrt(2*PI)*Math.exp(-eval[0]*eval[0]/2.0); // parabola scaled in y // eval[0] = tvalue; // eval[1] = Math.pow(tvalue,2)/400.0; // figure of eight // eval[0] = Math.sin(tvalue)*Math.cos(tvalue); // eval[1] = Math.sin(tvalue); // circle // eval[0] = Math.sin(tvalue); // eval[1] = Math.cos(tvalue); // limacon double a = -3.0; double b = 2.0; eval[0] = (b+a*Math.cos(tvalue))*Math.cos(tvalue); eval[1] = (b+a*Math.cos(tvalue))*Math.sin(tvalue); } public void paintComponent (Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; RenderingHints rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHints(rh); int width = getWidth(); int height = getHeight(); g2.setBackground(Color.white); AffineTransform transform = new AffineTransform(); transform.translate(550, 300); transform.scale(70, -70); GeneralPath path = new GeneralPath(); path.moveTo(plotPoints.get(0),plotPoints.get(1)); for (int j=4; j0) // { // g2.drawLine(i-1, previousY, i, y); // } else { // g2.drawLine(i, y, i, y); // } // previousY = y; // } } } }