import java.applet.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.util.*; interface PolarFunction { public abstract double r(double theta); } class Ellipse implements PolarFunction { final double a, b; //semimajor and semiminor axis final double a2, b2, a2b2; final double ratio; //b / a Ellipse(double initial_a, double initial_ratio) { b = (a = initial_a) * (ratio = initial_ratio); a2b2 = (a2 = a * a) * (b2 = b * b); } public double r(double theta) { //avoid division by zero if (a2 == 0 && b2 == 0) { return 0; } final double sin = Math.sin(theta); if (Math.abs(sin) < .001 && b2 == 0) { return a; } final double cos = Math.cos(theta); if (Math.abs(cos) < .001 && a2 == 0) { return b; } return Math.sqrt(a2b2 / (a2 * sin * sin + b2 * cos * cos)); } } //Logarithmic spiral r = a * b ** theta class Spiral implements PolarFunction { final double a; //value of function when theta == 0 final double lnb; Spiral(double initial_a, double b) { //tan pitch == ln b a = initial_a; lnb = Math.log(b); } public double r(double theta) { //return a * Math.pow(b, theta); return a * Math.exp(theta * lnb); } } //nPoints is number of points at which to plot the function, //including begin and end. class PolarRange { final int n; //number of intervals final double begin, end; //values for theta PolarRange(int nPoints, double initial_begin, double initial_end) { n = nPoints - 1; begin = initial_begin; end = initial_end; } PolarRange(int nPoints) {this(nPoints, 0, 2 * Math.PI);} //One more than a multiple of four, to evaluate the functions at //0, pi/2, pi, 3 * pi / 2 public static final PolarRange standard = new PolarRange(201, 0, 2 * Math.PI); } interface Paintable { abstract void paint(Graphics g); } class Disk implements Paintable { final int radius; final double r, theta; //center (default, at origin) Disk(int initial_radius, double initial_r, double initial_theta) { radius = initial_radius; r = initial_r; theta = initial_theta; } Disk(int initial_radius) { this(initial_radius, 0, 0); } public void paint(Graphics g) { g.fillOval( (int)Math.round(r * Math.cos(theta) - radius), (int)Math.round(r * Math.sin(theta) - radius), 2 * radius, 2 * radius); } } class Line implements Paintable { final int x0, y0, x1, y1; Line(int initial_x0, int initial_y0, int initial_x1, int initial_y1) { x0 = initial_x0; y0 = initial_y0; x1 = initial_x1; y1 = initial_y1; } public void paint(Graphics g) { g.drawLine(x0, y0, x1, y1); } } //The tail of the arrow is at (r0, theta0), and it points to (r1, theta1). class Arrow implements Paintable { static final int length = 223 / 10; final double r0, theta0; //location of tail final double r1, theta1; final double x0, y0, x1, y1; final double th; Arrow( double initial_r0, double initial_theta0, double initial_r1, double initial_theta1) { r0 = initial_r0; theta0 = initial_theta0; r1 = initial_r1; theta1 = initial_theta1; x0 = r0 * Math.cos(theta0); y0 = r0 * Math.sin(theta0); x1 = r1 * Math.cos(theta1); y1 = r1 * Math.sin(theta1); th = Math.atan2(y1 - y0, x1 - x0); } public void paint(Graphics g) { g.drawLine( (int)Math.round(x0), (int)Math.round(y0), (int)Math.round(x0 + length * Math.cos(th)), (int)Math.round(y0 + length * Math.sin(th)) ); } } class PolarGraph implements Paintable { final PolarFunction function; final PolarRange range; final double rotation; PolarGraph(PolarFunction initial_function, PolarRange initial_range, double initial_rotation) { function = initial_function; range = initial_range; rotation = initial_rotation; } PolarGraph(PolarFunction initial_function, PolarRange initial_range) { this(initial_function, initial_range, 0); } public void paint(Graphics g) { int x[] = new int[range.n + 1]; int y[] = new int[range.n + 1]; for (int i = 0; i <= range.n; ++i) { double theta = (range.begin * (range.n - i) + range.end * i) / range.n; final double r = function.r(theta); theta += rotation; x[i] = (int)Math.round( r * Math.cos(theta)); y[i] = (int)Math.round(-r * Math.sin(theta)); } g.drawPolyline(x, y, x.length); } } interface Factory { abstract Paintable paintable(int frame, int nFrames); } class DiskFactory implements Factory { final int radius0, radius1; final double r0, r1, theta0, theta1; DiskFactory( int initial_radius0, int initial_radius1, double initial_r0, double initial_r1, double initial_theta0, double initial_theta1) { radius0 = initial_radius0; radius1 = initial_radius1; r0 = initial_r0; r1 = initial_r1; theta0 = initial_theta0; theta1 = initial_theta1; } DiskFactory(int initial_radius0, int initial_radius1) { this(initial_radius0, initial_radius1, 0, 0, 0, 0); } public Paintable paintable(int frame, int nFrames) { return new Disk( in(radius0, radius1, frame, nFrames), in(r0, r1, frame, nFrames), in(theta0, theta1, frame, nFrames) ); } //interpolate static double in(double d0, double d1, int frame, int nFrames) { return (d0 * (nFrames - frame - 1) + d1 * frame) / (nFrames-1); } static int in(int i0, int i1, int frame, int nFrames) { return (i0 * (nFrames - frame - 1) + i1 * frame) / (nFrames-1); } } //(r0, theta0) is the tail. Arrow points to (r1, theta1). class ArrowFactory implements Factory { double r00, r01; double theta00, theta01; double r10, r11; double theta10, theta11; ArrowFactory( double initial_r00, double initial_r01, double initial_theta00, double initial_theta01, double initial_r10, double initial_r11, double initial_theta10, double initial_theta11 ) { r00 = initial_r00; r01 = initial_r01; theta00 = initial_theta00; theta01 = initial_theta01; r10 = initial_r10; r11 = initial_r11; theta10 = initial_theta10; theta11 = initial_theta11; } public Paintable paintable(int frame, int nFrames) { return new Arrow( in(r00, r01, frame, nFrames), in(theta00, theta01, frame, nFrames), in(r10, r11, frame, nFrames), in(theta10, theta11, frame, nFrames) ); } //interpolate static double in(double d0, double d1, int frame, int nFrames) { return (d0 * (nFrames - frame - 1) + d1 * frame) / (nFrames-1); } } class EllipseFactory implements Factory { final double a0, a1, ratio0, ratio1, tilt0, tilt1, begin0, begin1, end0, end1; final int nPoints0, nPoints1; EllipseFactory( double initial_a0, double initial_a1, double initial_ratio0, double initial_ratio1, double initial_tilt0, double initial_tilt1, int initial_nPoints0, int initial_nPoints1, double initial_begin0, double initial_begin1, double initial_end0, double initial_end1 ) { a0 = initial_a0; a1 = initial_a1; ratio0 = initial_ratio0; ratio1 = initial_ratio1; tilt0 = initial_tilt0; tilt1 = initial_tilt1; nPoints0 = initial_nPoints0; nPoints1 = initial_nPoints1; begin0 = initial_begin0; begin1 = initial_begin1; end0 = initial_end0; end1 = initial_end1; } EllipseFactory( double initial_a0, double initial_a1, double initial_ratio0, double initial_ratio1, double initial_tilt0, double initial_tilt1, int initial_nPoints0, int initial_nPoints1 ) { this( initial_a0, initial_a1, initial_ratio0, initial_ratio1, initial_tilt0, initial_tilt1, initial_nPoints0, initial_nPoints1, 0, 0, 2 * Math.PI, 2 * Math.PI); } public Paintable paintable(int frame, int nFrames) { return new PolarGraph( new Ellipse( in(a0, a1, frame, nFrames), in(ratio0, ratio1, frame, nFrames) ), new PolarRange( in(nPoints0, nPoints1, frame, nFrames), in(begin0, begin1, frame, nFrames), in(end0, end1, frame, nFrames) ), in(tilt0, tilt1, frame, nFrames) ); } //interpolate static double in(double d0, double d1, int frame, int nFrames) { return (d0 * (nFrames - frame - 1) + d1 * frame) / (nFrames-1); } static int in(int i0, int i1, int frame, int nFrames) { return (i0 * (nFrames - frame - 1) + i1 * frame) / (nFrames-1); } } /* A Projector has a Vector of Projects. Each Project has 1, 2, or 3 States. */ abstract class State { Projector projector; final String string; State(Projector initial_projector, String initial_string) { projector = initial_projector; string = initial_string; } String getString() {return string;} abstract Image getImage(); abstract void forward(); abstract void backward(); State overrideForward = null; State overrideBackward = null; } class Project { Projector projector; State aState[]; Project(Projector initial_projector) {projector = initial_projector;} } class Slide extends Project { Image image; final Graphics g; Slide(Projector initial_projector, String string) { super(initial_projector); image = projector.getImage(); g = projector.getChalk(image); aState = new State[1]; aState[0] = new State(projector, string) { Image getImage() {return image;} //This function is called by Projector.keyPressed after //we have advanced to the new state. That's why we //need a -1 to access the old state. void forward() {projector.dissolve(-1, 0);} void backward() {projector.dissolve(1, 0);} }; } } class Movie extends Project { Image image[] = new Image[2]; final int nFrames; //number of frames, including first and last final int milliseconds; //delay between frames Factory a[]; Movie(Projector initial_projector, String begin, String end, int initial_nFrames, int initial_milliseconds) { super(initial_projector); nFrames = initial_nFrames; milliseconds = initial_milliseconds; aState = new State[2]; aState[0] = new State(projector, begin) { Image getImage() { if (image[0] == null) { image[0] = projector.getImage(); paintFrame(image[0], 0, nFrames); } return image[0]; } void forward() {runForward();} void backward() { if (overrideBackward != null) { overrideBackward.backward(); } else { projector.dissolve(1, 0); } } }; aState[1] = new State(projector, end) { Image getImage() { if (image[1] == null) { image[1] = projector.getImage(); paintFrame(image[1], nFrames-1,nFrames); } return image[1]; } void forward() { if (overrideForward != null) { overrideForward.forward(); } else { projector.dissolve(-1, 0); } } void backward() {runBackward();} }; } void runForward() { //Frame 0 is currently displayed, no need to display it again. for (int frame = 1; frame < nFrames; ++frame) { Image image = projector.getImage(); paintFrame(image, frame, nFrames); final Image oldImage = projector.currentImage; projector.setCurrentImage(image); if (frame > 1) { oldImage.flush(); } if (frame < nFrames - 1) { Projector.sleep(milliseconds); } } } void runBackward() { //Frame nFrames - 1 is currently displayed, //no need to display it again. for (int frame = nFrames - 2; frame >= 0; --frame) { Image image = projector.getImage(); paintFrame(image, frame, nFrames); final Image oldImage = projector.currentImage; projector.setCurrentImage(image); if (frame < nFrames - 2) { oldImage.flush(); } if (frame > 0) { Projector.sleep(milliseconds); } } } void paintFrame(Image image, int frame, int nFrames) { if (a != null) { final Graphics g = projector.getChalk(image); for (int i = 0; i < a.length; ++i) { a[i].paintable(frame, nFrames).paint(g); } } } } class LoopThread extends Thread { final Loop loop; final boolean forward; LoopThread(Loop initial_loop, boolean initial_forward) { loop = initial_loop; forward = initial_forward; } public void run() { while (!interrupted()) { if (forward) { loop.runForward(); } else { loop.runBackward(); } } } } class Loop extends Project { //continuous loop Image image[] = new Image[2]; final int nFrames; //number of frames final int milliseconds; //delay between frames Factory a[]; LoopThread thread = null; Loop(Projector initial_projector, String begin, String middle, String end, int initial_nFrames, int initial_milliseconds) { super(initial_projector); nFrames = initial_nFrames; milliseconds = initial_milliseconds; aState = new State[3]; aState[0] = new State(projector, begin) { Image getImage() { if (image[0] == null) { image[0] = projector.getImage(); paintFrame(image[0], 0, nFrames); } return image[0]; } void forward() { thread = new LoopThread(Loop.this, true); thread.start(); } void backward() { if (overrideBackward != null) { overrideBackward.backward(); } else { projector.dissolve(1, 0); } } }; aState[1] = new State(projector, middle) { Image getImage() { if (image[0] == null) { image[0] = projector.getImage(); paintFrame(image[0], 0, nFrames); } return image[0]; } void forward() {thread.interrupt();} void backward() {thread.interrupt();} }; aState[2] = new State(projector, end) { Image getImage() { if (image[1] == null) { image[1] = projector.getImage(); paintFrame(image[1], nFrames-1,nFrames); } return image[1]; } void forward() { if (overrideForward != null) { overrideForward.forward(); } else { projector.dissolve(-1, 0); } } void backward() { thread = new LoopThread(Loop.this, false); thread.start(); } }; } void runForward() { for (int frame = 1; frame < nFrames; ++frame) { Image image = projector.getImage(); paintFrame(image, frame, nFrames); final Image oldImage = projector.currentImage; projector.setCurrentImage(image); if (frame > 0) { oldImage.flush(); } Projector.sleep(milliseconds); } final Image oldImage = projector.currentImage; projector.setCurrentImage(projector.getState(0).getImage()); if (nFrames > 1) { oldImage.flush(); } } void runBackward() { for (int frame = nFrames - 2; frame >= 0; --frame) { Image image = projector.getImage(); paintFrame(image, frame, nFrames); final Image oldImage = projector.currentImage; projector.setCurrentImage(image); if (frame < nFrames - 2) { oldImage.flush(); } Projector.sleep(milliseconds); } final Image oldImage = projector.currentImage; projector.setCurrentImage(projector.getState(0).getImage()); if (nFrames > 1) { oldImage.flush(); } } void paintFrame(Image image, int frame, int nFrames) { final Graphics g = projector.getChalk(image); for (int i = 0; i < a.length; ++i) { a[i].paintable(frame, nFrames).paint(g); } } } /* Milky Way has four arms. Logarithmic spiral r = ab ** theta Pitch of Milky Way's spiral arms is 12 degrees. ln b = tan 12 degrees, so b = Math.exp(Math.tan(12 * Math.PI / 180)). Each arm will be of length 360 + 12 degrees to end tangent to vertical or horizontal line. Arm that starts at theta = pi/2 will end at horizontal line y = iMicro.height / 2 - 5. Therefore a = (iMicro.height / 2 - 5) / Math.pow(b, 12 * Math.PI / 180) */ class Galaxy implements Factory { static double pitch = 12 * Math.PI / 180, b = Math.exp(Math.tan(pitch)), a = (223.0 / 2 - 5) / Math.pow(b, 2 * Math.PI + pitch); static int narms = 4; static int nPoints = 200; //points per arm static final PolarRange armRange = new PolarRange(nPoints, -3 * Math.PI, 2 * Math.PI + pitch); static final int n = 1400; static final double height = 223.0 / 30; static double x[] = new double[n], y[] = new double[n], z[] = new double[n]; static Random random = new Random(); static { for (int i = 0; i < n; ++i) { double theta = (random.nextDouble() * 5 - 3) * Math.PI; final double dr = 2.75 * random.nextGaussian(), r = a * Math.pow(b, theta) + dr; theta += random.nextInt() % 4 * 2 * Math.PI / 4; x[i] = r * Math.cos(theta); y[i] = r * Math.sin(theta); z[i] = random.nextGaussian() * height * height * height / (2 * r * Math.pow(r, 1.0 / 2.0) + height * height); if (Math.abs(z[i]) > height) { z[i] /= 2; } } } public Paintable paintable(final int frame, final int nFrames){ return new Paintable() { public void paint(Graphics g) { for (int i = 0; i < n; ++i) { //Cylindrical coordinates final double r = Math.sqrt(y[i]*y[i] + z[i] * z[i]), theta = Math.atan2(z[i], -y[i]) + Math.PI * frame / (2 * (nFrames - 1)); g.drawLine( (int)Math.round(x[i]), (int)Math.round(r * Math.sin(theta)), (int)Math.round(x[i]), (int)Math.round(r * Math.sin(theta))); } } }; } } class Tracing implements Paintable { public void paint(Graphics g) { for (int i = 0; i < Galaxy.narms; ++i) { new PolarGraph( new Spiral(Galaxy.a, Galaxy.b), Galaxy.armRange, i * 2 * Math.PI / Galaxy.narms ).paint(g); } } } public class Projector extends Applet implements KeyListener { Vector vState = new Vector(); //Vector of States int iState = -1; //index of current state final int margin = 10; //Display this number of lines before & after current state final int nLines = 2; final int lineHeight = 20; static final Font font = new Font("Serif", Font.PLAIN, 18); final FontMetrics metrics = getFontMetrics(font); final int ascent = metrics.getMaxAscent(); final Dimension iMicro = new Dimension(383, 223), dim = new Dimension(iMicro.width + 2 * margin, iMicro.height + 2 * margin); Image currentImage; int aold[], anew[], amix[]; //for dissolve public void init() { aold = new int[dim.width * dim.height]; anew = new int[dim.width * dim.height]; amix = new int[dim.width * dim.height]; final Slide empty_slide = new Slide(this, "empty slide"); add(new Slide(this, iMicro.width + " \u00D7 " + iMicro.height + " target rectangle with crosshairs") { { final int w = iMicro.width, h = iMicro.height; final Rectangle r = new Rectangle(-w / 2, -h / 2, w, h); g.drawRect(r.x, r.y, r.width, r.height); //vertical line final int x = r.x + r.width / 2; g.drawLine(x, r.y, x, r.y + r.height); //horizontal line final int y = r.y + r.height / 2; g.drawLine(r.x, y, r.x + r.width, y); //inner rectangle final int n = 5; g.drawRect(n - w/2, n - h/2, w - 2*n, h - 2*n); } }); add(empty_slide); final String solar_system = "Solar System: planets orbit in same plane"; add(new Movie(this, "1950's atom: electrons in different planes", solar_system, 25, 50 ) { { final double ratio = .5, //minor to major axis biga = iMicro.height / 2, bigb = biga * ratio, littlea = bigb * 2 / 3, littleb = littlea * ratio; int k = iMicro.height / 15; a = new Factory[6]; a[0] = new EllipseFactory( biga, biga, ratio, ratio, 0, 0, 201, 201); a[1] = new EllipseFactory( biga, biga - k, ratio, ratio, Math.PI / 3, 0, 201, 201); a[2] = new EllipseFactory( biga, biga - 2 * k, ratio, ratio, -Math.PI / 3, 0, 201, 201); a[3] = new EllipseFactory( littlea, littlea + k, ratio, ratio, 0, 0, 201, 201); a[4] = new EllipseFactory( littlea, littlea, ratio, ratio, Math.PI / 2, 0, 201, 201); a[5] = new DiskFactory( iMicro.height / 30, iMicro.height / 30); } }); add(new Movie(this, solar_system, "Solar System: edge-on view", 25, 50 ) { { final double ratio = .5, //minor to major axis biga = iMicro.height / 2, bigb = biga * ratio, littlea = bigb * 2 / 3, littleb = littlea * ratio; int k = iMicro.height / 15; a = new Factory[6]; a[0] = new EllipseFactory( biga, biga, ratio, 0, 0, 0, 201, 201); a[1] = new EllipseFactory( biga - k, biga - k, ratio, 0, 0, 0, 201, 201); a[2] = new EllipseFactory( biga - 2 * k, biga - 2 * k, ratio, 0, 0, 0, 201, 201); a[3] = new EllipseFactory( littlea + k, littlea + k, ratio, 0, 0, 0, 201, 201); a[4] = new EllipseFactory( littlea, littlea, ratio, 0, 0, 0, 201, 201); a[5] = new DiskFactory( iMicro.height / 30, iMicro.height / 30); } }); add(empty_slide); /* final double marsRadius = iMicro.height / 2 - 5, earthRadius = .625 * marsRadius; // * 93 / 228; final String heliocentric = "Heliocentric view of Mars"; add(new Loop(this, heliocentric, "running", heliocentric, 150, 50 ) { { a = new Factory[4]; a[0] = new DiskFactory( //sun iMicro.height / 30, iMicro.height / 30); a[1] = new EllipseFactory( //orbit of Mars marsRadius, marsRadius, 1, 1, 0, 0, 201, 201); a[2] = new DiskFactory( //Mars iMicro.height / 45, iMicro.height / 45, marsRadius, marsRadius, 0, -2 * Math.PI); a[3] = new ArrowFactory( 0, 0, 0, 0, marsRadius, marsRadius, 0, -2 * Math.PI); } }); final String geocentric = "Geocentric view of Mars from stationary Earth"; add(new Loop(this, geocentric, "running", geocentric, 150, 50 ) { { a = new Factory[6]; a[0] = new DiskFactory( //sun iMicro.height / 30, iMicro.height / 30); a[1] = new EllipseFactory( //orbit of Mars marsRadius, marsRadius, 1, 1, 0, 0, 201, 201); a[2] = new DiskFactory( //Mars iMicro.height / 45, iMicro.height / 45, marsRadius, marsRadius, 0, -2 * Math.PI); a[3] = new EllipseFactory( //orbit of Earth earthRadius, earthRadius, 1, 1, 0, 0, 201, 201); a[4] = new DiskFactory( //Earth iMicro.height / 45, iMicro.height / 45, earthRadius, earthRadius, 0, 0); a[5] = new ArrowFactory( earthRadius, earthRadius, 0, 0, marsRadius, marsRadius, 0, -2 * Math.PI); } }); final String geomove = "Geocentric view of Mars from moving Earth"; add(new Loop(this, geomove, "running", geomove, 150, 50 ) { { a = new Factory[6]; a[0] = new DiskFactory( //sun iMicro.height / 30, iMicro.height / 30); a[1] = new EllipseFactory( //orbit of Mars marsRadius, marsRadius, 1, 1, 0, 0, 201, 201); a[2] = new DiskFactory( //Mars iMicro.height / 45, iMicro.height / 45, marsRadius, marsRadius, 0, -2 * Math.PI); a[3] = new EllipseFactory( //orbit of Earth earthRadius, earthRadius, 1, 1, 0, 0, 201, 201); a[4] = new DiskFactory( //Earth iMicro.height / 45, iMicro.height / 45, earthRadius, earthRadius, 0, -4 * Math.PI); a[5] = new ArrowFactory( earthRadius, earthRadius, 0, -4 * Math.PI, marsRadius, marsRadius, 0, -2 * Math.PI); } }); add(empty_slide); */ final String jawza = "\u062C\u0648\u0630\u0627", el_jawza = "\u0627\u0644" + jawza, yad_el_jawza = "\u064A\u062F " + el_jawza; final Font arabic = new Font("Serif", Font.PLAIN, 72); final FontMetrics metrics = getFontMetrics(arabic); final int width = metrics.stringWidth(yad_el_jawza), height = metrics.getHeight() / 2; add(new Slide(this, "Jawza: Central One (i.e., Orion)") { { g.setFont(arabic); g.drawString(jawza, -width / 2, -height / 2); } }); add(new Slide(this, "el Jawza: the Central One") { { g.setFont(arabic); g.drawString(el_jawza, -width / 2, -height / 2); } }); add(new Slide(this, "Yad el Jawza: Hand of the Central One") { { g.setFont(arabic); g.drawString(yad_el_jawza, -width / 2, -height / 2); } }); add(new Slide(this, "Yad el Jawza and Bad el Jawza") { { g.setFont(arabic); g.drawString(yad_el_jawza, -width / 2, -height / 2); g.drawString("\u0628\u062F " + el_jawza, -width / 2, 3 * height / 2); } }); add(empty_slide); add(new Movie(this, "Galaxy: edge-on view", "Galaxy from above", 40, 50 ) { { a = new Factory[1]; a[0] = new Galaxy(); } }); add(new Slide(this, "trace the arms: " + "logarithmic spiral r = a * b ** \u03B8") { { new Galaxy().paintable(1, 2).paint(g); new Tracing().paint(g); } }); final Slide just_the_arms = new Slide(this, "just the arms (and the black hole)") { { new Tracing().paint(g); } }; add(just_the_arms); final Spiral oneArm = new Spiral(Galaxy.a * Math.pow(Galaxy.b, -Math.PI / 2), Galaxy.b); final PolarRange oneArmRange = new PolarRange(Galaxy.nPoints, Math.PI / 2, 5 * Math.PI / 2 + Galaxy.pitch); final Slide just_one_arm = new Slide(this, "just one arm") { { new PolarGraph(oneArm, oneArmRange).paint(g); } }; add(just_one_arm); final int nRadii = 5, tanLength = 50; final Line radius[] = new Line[nRadii], tangent[] = new Line[nRadii]; for (int i = 0; i < nRadii; ++i) { final double theta = 3 * Math.PI / 2 + i * Math.PI / 4, th = theta - Math.PI / 2 - Galaxy.pitch, r = oneArm.r(theta), x = r * Math.cos(theta), y = -r * Math.sin(theta); radius[i] = new Line(0, 0, (int)Math.round(x), (int)Math.round(y)); tangent[i] = new Line( radius[i].x1, radius[i].y1, (int)Math.round(x + tanLength * Math.cos(th)), (int)Math.round(y - tanLength * Math.sin(th))); } add(new Slide(this, "Draw the radii") { { new PolarGraph(oneArm, oneArmRange).paint(g); for (int i = 0; i < nRadii; ++i) { radius[i].paint(g); } } }); add(new Slide(this, "Add the tangents; pitch angle always 12\u00B0.") { { new PolarGraph(oneArm, oneArmRange).paint(g); for (int i = 0; i < nRadii; ++i) { radius[i].paint(g); tangent[i].paint(g); } } }); final Line straightHawk = new Line( -iMicro.width / 2, -iMicro.height / 2 + 5 + 1, (int)Math.round(-iMicro.height * Math.tan(Galaxy.pitch) / 2), -iMicro.height / 2 + 5 + 1 ); final int straightHawkLength = dim.width / 2 - straightHawk.x1; final int nSights = 4; final Line sight[] = new Line[nSights]; for (int i = 0; i < nSights; ++i) { final int x = straightHawk.x1 - straightHawkLength * i / (nSights + 1); sight[i] = new Line( x, straightHawk.y0, i == 0 ? 0 : (int)Math.round(x + 50 * Math.sin(Galaxy.pitch)), i == 0 ? 0 : (int)Math.round(straightHawk.y0 + 50 * Math.cos(Galaxy.pitch)) ); } final Disk food = new Disk(iMicro.height / 60); add(new Slide(this, "Hawk flies looking to one side.") { { straightHawk.paint(g); food.paint(g); final Font font = new Font("SansSerif", Font.PLAIN, 20); g.setFont(font); final FontMetrics metrics = g.getFontMetrics(); final String string = "FOOD "; g.drawString(string, -metrics.stringWidth(string), metrics.getHeight() -metrics.getMaxAscent() / 2); for (int i = 0; i < nSights; ++i) { sight[i].paint(g); } } }); add(new Slide(this, "Hawk spirals in towards prey, bug to light.") { { straightHawk.paint(g); food.paint(g); for (int i = 0; i < nSights; ++i) { sight[i].paint(g); } new PolarGraph(oneArm, oneArmRange).paint(g); for (int i = 0; i < nRadii; ++i) { radius[i].paint(g); tangent[i].paint(g); /* new Line( radius[i].x1, radius[i].y1, (int)Math.round(radius[i].x1/2), (int)Math.round(radius[i].y1/2) ).paint(g); */ } } }); add(just_one_arm); add(just_the_arms); add(empty_slide); final String orbit_ellipse = "Stars orbiting in ellipses."; add(new Movie(this, "How do arms form? Stars orbiting in circles.", orbit_ellipse, 25, 50 ) { { final int n = 11; //number of orbits a = new Factory[n]; for (int i = 0; i < n; ++i) { final int radius = (i + 1) * (iMicro.height / 2 - 5) / n; a[i] = new EllipseFactory( radius, radius, 1, 2.0 / 3.0, 0, 0, 201, 201); } } }); add(new Movie(this, orbit_ellipse, "The ellipses have different orientations.", 25, 50 ) { { final int n = 11; //number of orbits a = new Factory[n]; for (int i = 0; i < n; ++i) { final int radius = (i + 1) * (iMicro.height / 2 - 5) / n; a[i] = new EllipseFactory( radius, radius, 2.0 / 3.0, 2.0 / 3.0, 0, -(2.0 / 3.0) * Math.PI * (n - i - 1) / (n - 1), 201, 201); } } }); add(empty_slide); update(getGraphics()); addKeyListener(this); requestFocus(); } //Return the State that is off away from the current State. int getI(int off) { final int size = vState.size(); for (off += iState; off < 0; off += size) { } return off % size; } State getState(int off) {return (State)vState.elementAt(getI(off));} void add(State state) { if (vState.isEmpty()) { vState.addElement(state); iState = 0; setCurrentImage(state.getImage()); } else { //Merge two states. State lastState = (State)vState.lastElement(); if (state.getString().equals(lastState.getString())) { lastState.overrideForward = state; state.overrideBackward = lastState; } else { vState.addElement(state); } } } void add(Project project) { for (int i = 0; i < project.aState.length; ++i) { add(project.aState[i]); } } Image getImage() { Image image = createImage(dim.width, dim.height); final Graphics g = image.getGraphics(); g.setColor(Color.black); g.fillRect(0, 0, dim.width, dim.height); return image; } Graphics getChalk(Image image) { final Graphics g = image.getGraphics(); g.setColor(Color.white); g.translate(dim.width / 2, dim.height / 2); return g; } public void keyPressed(KeyEvent ev) { if (ev.getID() == KeyEvent.KEY_PRESSED) { final int code = ev.getKeyCode(), old = getI(0); final State oldState = getState(0); final Graphics g = getGraphics(); if (code == KeyEvent.VK_DOWN) { //go forward iState = getI(1); System.out.println( "State" + old + " (\"" + oldState.getString() + "\") to " + iState + " (\"" + getState(0).getString() + "\")"); updateCaptions(g); oldState.forward(); } else if (code == KeyEvent.VK_UP) { //go backward iState = getI(-1); System.out.println( "State" + old + " (\"" + oldState.getString() + "\") to " + iState + " (\"" + getState(0).getString() + "\")"); updateCaptions(g); oldState.backward(); } } System.out.println( "freeMemory == " + Runtime.getRuntime().freeMemory() + ", " + "totalMemory == " + Runtime.getRuntime().totalMemory() ); } public void keyReleased(KeyEvent ev) {} public void keyTyped(KeyEvent ev) {} void updateCaptions(Graphics g) { g.setColor(Color.black); g.fillRect(0, dim.height + lineHeight, dim.width, (2 * nLines + 1) * lineHeight); g.setFont(font); for (int i = -nLines; i <= nLines; ++i) { g.setColor(i == 0 ? Color.white : Color.gray); g.drawString(1 + getI(i) + ": " + getState(i).getString(), margin, dim.height + (i + nLines + 1) * lineHeight + ascent); } } void updateImage(Graphics g) { g.drawImage(currentImage, 0, 0, null); } void dissolve(int oldOffset, int newOffset) { final Image oldImage = getState(oldOffset).getImage(), newImage = getState(newOffset).getImage(); if (oldImage == null) { System.err.println("null oldImage in dissolve"); } if (newImage == null) { System.err.println("null newImage in dissolve"); } final PixelGrabber oldgrabber = new PixelGrabber(oldImage, 0, 0, dim.width, dim.height, aold, 0, dim.width), newgrabber = new PixelGrabber(newImage, 0, 0, dim.width, dim.height, anew, 0, dim.width); try { oldgrabber.grabPixels(); newgrabber.grabPixels(); } catch (InterruptedException e) { System.err.println("interrupted waiting for pixels"); return; } final Graphics g = getGraphics(); final int n = 10; //number of intermediate steps for (int i = 1; i <= n; ++i) { for (int j = 0; j < amix.length; ++j) { final int oldr = aold[j] >> 16 & 0xFF, oldg = aold[j] >> 8 & 0xFF, oldb = aold[j] & 0xFF, newr = anew[j] >> 16 & 0xFF, newg = anew[j] >> 8 & 0xFF, newb = anew[j] & 0xFF, //Compute weighted average. mixr = oldr + (newr - oldr) * i / (n+1), mixg = oldg + (newg - oldg) * i / (n+1), mixb = oldb + (newb - oldb) * i / (n+1); amix[j] = 255 << 24 //alpha channel: opaque | mixr << 16 | mixg << 8 | mixb; } //Copy weighted average into current. Image oldIm = currentImage; setCurrentImage(createImage(new MemoryImageSource( dim.width, dim.height, amix, 0, dim.width ))); if (i > 1) { oldIm.flush(); } if (i < n) { sleep(75); } } setCurrentImage(newImage); } void setCurrentImage(Image image) { currentImage = image; updateImage(getGraphics()); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { updateCaptions(g); updateImage(g); final int size = vState.size(); final String string = "\u2014 " + size + " state" + (size == 0 ? "" : "s") + " \u2014"; g.setColor(Color.black); g.fillRect(0, dim.height, dim.width, lineHeight); g.setFont(font); g.setColor(Color.gray); g.drawString(string, (dim.width - metrics.stringWidth(string)) / 2, dim.height + ascent); } static public void sleep(int milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { //System.err.println("sleep interrupted"); Thread.currentThread().interrupt(); } } }