// SpinIllusion.java
// by Brett Allen
// version 1.0: June 25, 2001
//
// based on Sun's "BezierAnim" example

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.*;

public class SpinIllusion extends JApplet {
    Demo demo;

    public void init() {
        getContentPane().add(demo = new Demo());
        getContentPane().add("North", new DemoControls(demo));
    }

    public void start() {
        demo.start();
    }	
  
    public void stop() {
        demo.stop();
    }


    /**
     * The Demo class performs the animation and the painting.
     */
    public class Demo extends JPanel implements Runnable {
        private Thread thread;
        private BufferedImage bimg;

		protected double skew = 0.3;
		protected int numAround = 30;
		protected int numHigh = 2;
		protected double pWidth = 10;
		protected double pHeight = 10;
		protected double pScale = 1.0;
		protected double relHeight = 0.1;
		protected int pFrame = 0;
		protected int pSpeed = 0;
		protected boolean shading = true;
    
        public Demo() {
            setBackground(Color.lightGray);
        }
    
        
        public void reset(int w, int h) {
        }
    

        public void step(int w, int h) {
			if (pSpeed == 0) {
				pScale = 1.0;
				return;
			}
			
			int max = 40 - pSpeed * 2;
			pFrame = (pFrame + 1) % max;
			if (pFrame > max/2) {
				pScale = 0.5 + (pFrame - max/2.0) / max;
			}
			else {
				pScale = 0.5 + (max/2.0 - pFrame) / max;
			}
        }
    

		protected void drawSeg(boolean parity, Graphics2D g2) {
			AffineTransform oldAF = g2.getTransform();
			if (parity)
				g2.shear(skew, 0);
			else
				g2.shear(-skew, 0);

			g2.setPaint(Color.white);
			if (shading) g2.setPaint(parity?Color.white:Color.gray);
			g2.draw(new Line2D.Double(-pWidth/2.0, -pHeight/2.0, -pWidth/2.0, pHeight/2.0));
			if (shading) g2.setPaint(parity?Color.white:Color.white);
			g2.draw(new Line2D.Double(-pWidth/2.0, pHeight/2.0, pWidth/2.0, pHeight/2.0));
			if (shading) g2.setPaint(parity?Color.gray:Color.white);
			g2.draw(new Line2D.Double(pWidth/2.0, pHeight/2.0, pWidth/2.0, -pHeight/2.0));
			if (shading) g2.setPaint(parity?Color.gray:Color.gray);
			g2.draw(new Line2D.Double(pWidth/2.0, -pHeight/2.0, -pWidth/2.0, -pHeight/2.0));

			g2.setTransform(oldAF);
		}    

        // sets the points of the path and draws and fills the path
        public void drawDemo(int w, int h, Graphics2D g2) {
			int i, j;

			AffineTransform oldAF = g2.getTransform();

			Dimension d = getSize();
			g2.translate(d.width / 2.0, d.height / 2.0);
			g2.scale(pScale, pScale);
			AffineTransform curAF = g2.getTransform();
			double rad = Math.min(d.width / 2.0, d.height / 2.0);

			g2.setPaint(Color.black);
			g2.fillOval(-2, -2, 4, 4);

			pHeight = rad*relHeight;
			pWidth = 2.0 * Math.PI * (rad - pHeight) / (2.0 * numAround);

			for (j=0; j < numHigh; j++) {
				double curRad = rad - (j*2 + 1) * pHeight;
				int newNumAround = (int)(1.1 * numAround * curRad / rad);

				for (i=0; i < newNumAround; i++) {
					g2.setTransform(curAF);
					g2.rotate(i * 2.0 * Math.PI / newNumAround, 0, 0);
					g2.translate(0, curRad);
					drawSeg(j%2 == 0, g2);
				}
			}

			g2.setTransform(oldAF);
        }
    
    
        public Graphics2D createGraphics2D(int w, int h) {
            Graphics2D g2 = null;
            if (bimg == null || bimg.getWidth() != w || bimg.getHeight() != h) {
                bimg = (BufferedImage) createImage(w, h);
                reset(w, h);
            } 
            g2 = bimg.createGraphics();
            g2.setBackground(getBackground());
            g2.clearRect(0, 0, w, h);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
            return g2;
        }
    
    
        public void paint(Graphics g) {
            Dimension d = getSize();
            step(d.width, d.height);
            Graphics2D g2 = createGraphics2D(d.width, d.height);
            drawDemo(d.width, d.height, g2);
            g2.dispose();
            if (bimg != null)  {
                g.drawImage(bimg, 0, 0, this);
            }
        }
    
    
        public void start() {
            thread = new Thread(this);
            thread.setPriority(Thread.MIN_PRIORITY);
            thread.start();
        }
    
    
        public synchronized void stop() {
            thread = null;
        }
    
    
        public void run() {
            Thread me = Thread.currentThread();
            while (thread == me) {
                repaint();
                try {
                    thread.sleep(5);
                } catch (Exception e) { break; }
            }
            thread = null;
        }
    } // End Demo class
    


    /**
     * The DemoControls class controls fills and strokes.
     */
    static class DemoControls extends JPanel implements ChangeListener {
        Demo demo;
        
		JSlider skewSlider;
		JSlider animSlider;
		JSlider heightSlider, rowSlider;
		JCheckBox shadingCB;
        Thread thread;

        public DemoControls(Demo demo) {
			JPanel tempPanel;
            this.demo = demo;

			setLayout(new GridLayout(5, 1));

			tempPanel = new JPanel();
				tempPanel.add(new JLabel("Skew:"));
				skewSlider = new JSlider(JSlider.HORIZONTAL, -10, 10, 5);
				skewSlider.addChangeListener(this);
				skewSlider.setMajorTickSpacing(5);
				skewSlider.setMinorTickSpacing(1);
				skewSlider.setPaintTicks(true);
				skewSlider.setPaintLabels(true);
				skewSlider.setSnapToTicks(true);
				tempPanel.add(skewSlider);
			add(tempPanel);

			tempPanel = new JPanel();
				tempPanel.add(new JLabel("Row height:"));
				heightSlider = new JSlider(JSlider.HORIZONTAL, 1, 20, 10);
				heightSlider.addChangeListener(this);
				heightSlider.setMajorTickSpacing(5);
				heightSlider.setMinorTickSpacing(1);
				heightSlider.setPaintTicks(true);
				heightSlider.setPaintLabels(true);
				heightSlider.setSnapToTicks(true);
				tempPanel.add(heightSlider);
			add(tempPanel);

			tempPanel = new JPanel();
				tempPanel.add(new JLabel("# of rows:"));
				rowSlider = new JSlider(JSlider.HORIZONTAL, 1, 6, 1);
				rowSlider.addChangeListener(this);
				rowSlider.setMajorTickSpacing(1);
				rowSlider.setMinorTickSpacing(1);
				rowSlider.setPaintTicks(true);
				rowSlider.setPaintLabels(true);
				rowSlider.setSnapToTicks(true);
				tempPanel.add(rowSlider);
			add(tempPanel);

			tempPanel = new JPanel();
				tempPanel.add(new JLabel("Animation:"));
				animSlider = new JSlider(JSlider.HORIZONTAL, 0, 10, 0);
				animSlider.addChangeListener(this);
				animSlider.setMajorTickSpacing(5);
				animSlider.setMinorTickSpacing(1);
				animSlider.setPaintTicks(true);
				animSlider.setPaintLabels(true);
				animSlider.setSnapToTicks(true);
				tempPanel.add(animSlider);
			add(tempPanel);

			tempPanel = new JPanel();
				shadingCB = new JCheckBox("Shading");
				shadingCB.addChangeListener(this);
				shadingCB.setSelected(true);
				tempPanel.add(shadingCB);
			add(tempPanel);
        }

		public void stateChanged(ChangeEvent e) {
			if (e.getSource() == skewSlider) {
				demo.skew = skewSlider.getValue() / 10.0;
			}
			else if (e.getSource() == heightSlider) {
				demo.relHeight = heightSlider.getValue() / 100.0;
			}
			else if (e.getSource() == animSlider) {
				demo.pSpeed = animSlider.getValue();
			}
			else if (e.getSource() == rowSlider) {
				demo.numHigh = rowSlider.getValue();
			}
			else if (e.getSource() == shadingCB) {
				demo.shading = shadingCB.isSelected();
			}
		}

    } // End DemoControls class



    public static void main(String argv[]) {
        final SpinIllusion demo = new SpinIllusion();
        demo.init();
        Frame f = new Frame("Spin Illusion");
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
            public void windowDeiconified(WindowEvent e) { demo.start(); }
            public void windowIconified(WindowEvent e) { demo.stop(); }
        });
        f.add(demo);
        f.pack();
        f.setSize(new Dimension(500,600));
        f.show();
        demo.start();
    }
} // End SpinIllusion class

