import java.awt.geom.*;

/** Compute the right shape for the ascender cam.  This is an equiangular
 *  spiral with teeth.  (Much as I'd like to throw some gratuitous curves
 *  in here, looks like it will be straight lines, though.)
 *
 *  Design constraints:  cam spans 60-degrees and must fit inside 1 5/8"
 *  width.  The difference in radius between "open" (on the left) and
 *  "closed" (on the right) is 5/8" (equal to the width of the channel,
 *  and thus the maximal diameter of rope that will fit).  These
 *  constraints were sufficient to completely define the equiangular
 *  spiral.  Then I added 6 equally-spaced 1/4" deep triangular (sawtooth)
 *  teeth, with the sharp edges towards the left. */
public class MakeCam {
    static final int TEETH=6;
    static final double TOOTH_DEPTH=1/4.;
    static final double TOOTH_PROTRUDE=0; // 1 is all 1/4" outside the spiral
    
    static final double CAM_ARCSPAN = Math.toRadians(60);
    static final double CAM_WIDTH = 1+(5/8.);
    static final double CAM_DIFF = 5/8.+1/8.;

    static final double SHAFT_DIAM = 3/8.;
    static final double SHAFT_INSIDE_KEEPAWAY = 1/20.;
    static final double SHAFT_OUTSIDE_KEEPAWAY = 1/4.;

    //----------------- xfig info ---------------
    static final double XFIG_XOFFSET = 4;
    static final double XFIG_YOFFSET = 4;
    static final int XFIG_SCALE = 1200;

    public static void main(String[] args) {
	xfigInit();
	// equation of equiangular spiral is:
	//    r = a*exp(theta*cot(b))
	// where a and b are the parameters to be determined.
	// we let theta=0 be the small side.
	double alpha_plus_cam_diff = (CAM_WIDTH/2)/Math.sin((CAM_ARCSPAN)/2);
	double alpha = alpha_plus_cam_diff - CAM_DIFF;
	double cot_beta = Math.log(alpha_plus_cam_diff/alpha) / CAM_ARCSPAN;
	double beta = Math.atan(1/cot_beta);

	System.err.println("ALPHA: "+alpha+"\"");
	System.err.println("BETA: "+Math.toDegrees(beta)+" degrees");

	xfigAddPoint(polarToRect(-Math.toRadians(90)-(CAM_ARCSPAN/2),
				 (SHAFT_DIAM/2)+SHAFT_INSIDE_KEEPAWAY+
				 SHAFT_OUTSIDE_KEEPAWAY));

	for (int tooth=0; tooth<=TEETH; tooth++) {
	    double theta = CAM_ARCSPAN*tooth/TEETH;
	    double spiral_radius = alpha*Math.exp(theta*cot_beta);

	    if (tooth>0) {/* skip the 'end of last tooth' point for first pt.*/
		Point2D p = polarToRect(theta-(CAM_ARCSPAN/2),
					spiral_radius+
					TOOTH_PROTRUDE*TOOTH_DEPTH);
		xfigAddPoint(p);
	    }
	    // now do 'start of next tooth' point. skip for the last.
	    if (tooth<TEETH) {
		Point2D p = polarToRect(theta-(CAM_ARCSPAN/2),
					spiral_radius-
					(1-TOOTH_PROTRUDE)*TOOTH_DEPTH);
		xfigAddPoint(p);
	    }
	}
	// up around shaft and closed.
	xfigAddPoint(polarToRect(Math.toRadians(90)+(CAM_ARCSPAN/2),
				 (SHAFT_DIAM/2)+SHAFT_INSIDE_KEEPAWAY+
				 SHAFT_OUTSIDE_KEEPAWAY));
	xfigAddPoint(polarToRect(Math.toRadians(180),
				 (SHAFT_DIAM/2)+SHAFT_INSIDE_KEEPAWAY+
				 SHAFT_OUTSIDE_KEEPAWAY));
	xfigAddPoint(polarToRect(-Math.toRadians(90)-(CAM_ARCSPAN/2),
				 (SHAFT_DIAM/2)+SHAFT_INSIDE_KEEPAWAY+
				 SHAFT_OUTSIDE_KEEPAWAY));

	// now do shaft cutout.
	xfigCircle(polarToRect(0,0), SHAFT_INSIDE_KEEPAWAY+(SHAFT_DIAM/2));
	
	// done!
    }
    static Point2D polarToRect(double theta, double radius) {
	return new Point2D.Double
	    (radius*Math.cos(theta),
	     radius*Math.sin(theta));
    }
    static void xfigInit() {
	System.out.println("#FIG 3.2");
	System.out.println("Portrait");
	System.out.println("Center");
	System.out.println("Inches");
	System.out.println("Letter");
	System.out.println("100.00");
	System.out.println("Single");
	System.out.println("-2");
	System.out.print(XFIG_SCALE);
	System.out.println(" 2");
	// now start polyline.
	System.out.print("2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 ");
	System.out.println(2*TEETH+4);
    }
    static Point2D xfigTranslate(Point2D p) {
	return new Point2D.Double(p.getY()+XFIG_XOFFSET,-p.getX()+XFIG_YOFFSET);
    }
    static void xfigAddPoint(Point2D p) {
	System.out.print('\t');
	xfigAddPoint2(p);
	System.out.println();
    }
    static void xfigAddPoint2(Point2D p) {
	p = xfigTranslate(p);
	System.out.print(Math.round(p.getX()*XFIG_SCALE));
	System.out.print(' ');
	System.out.print(Math.round(p.getY()*XFIG_SCALE));
    }

    static void xfigCircle(Point2D center, double radius) {
	System.out.print("1 3 0 1 0 7 50 0 -1 0.000 1 0.0000 ");
	xfigAddPoint2(center);
	System.out.print(' ');
	System.out.print(Math.round(radius*XFIG_SCALE));
	System.out.print(' ');
	System.out.print(Math.round(radius*XFIG_SCALE));
	System.out.print(' ');
	xfigAddPoint2(center);
	System.out.print(' ');
	Point2D p = new Point2D.Double(center.getX()+radius, center.getY());
	xfigAddPoint2(p);
	System.out.println();
    }
}

