import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.lang.Math; import java.lang.reflect.Array; public class Helix extends JPanel { static JFrame frame; // Helix Element Interface. Anything will do, it is just used to sort // by depth interface HelixElement { public double depth (); } // Our Helix Element. Knows how to paint itself. class MyHelixElement implements HelixElement { double x,y,z; Color clr; double width, width2; public MyHelixElement (Color c, double w) { clr = c; width = w/2.0; width2 = w/4.0; } public void paint(Graphics g) { g.setColor(clr); // just for fun, let's paint the further ones as empty rects if (z > 0.0) g.fillRect((int)(x-width2), (int)(y-width2), (int)width, (int)width); else g.drawRect((int)(x-width2), (int)(y-width2), (int)width, (int)width); } public double depth () { return z; } } // A comparator for sorting the Helix Elements. class HelixElementComparator implements Comparator { public int compare(Object o1, Object o2) { HelixElement h1 = (HelixElement) o1; HelixElement h2 = (HelixElement) o2; if ((h1.depth() - h2.depth()) < 0.0) return -1; else if ((h1.depth() - h2.depth()) > 0.0) return 1; else return 0; } public boolean equals(Object o1, Object o2) { HelixElement h1 = (HelixElement) o1; HelixElement h2 = (HelixElement) o2; return (h1.depth() == h2.depth()); } } boolean debug = true; ////////////////////////////////////////////////// // The main class methods and vars // The full set of elements. MyHelixElement els[]; // A place to hold the sorted subset. We will grab a small set that // we know cover the screen, and sort them by Z. We'll then pain them // back to front. MyHelixElement sortEls[] = new MyHelixElement[10]; HelixElementComparator helixComp = new HelixElementComparator(); // Used by the mouse dragging routines. int currentX = 0; int currentY = 0; public Helix() { setDoubleBuffered(true); setBackground(Color.black); // set up a bogus set of data, for testing. els = new MyHelixElement[100]; int i; for (i=0; i<100; i+=10) { els[i] = new MyHelixElement(Color.red, 40); els[i+1] = new MyHelixElement(Color.green, 31); els[i+2] = new MyHelixElement(Color.pink, 44); els[i+3] = new MyHelixElement(Color.yellow, 33); els[i+4] = new MyHelixElement(Color.orange, 35); els[i+5] = new MyHelixElement(Color.darkGray, 31); els[i+6] = new MyHelixElement(Color.cyan, 50); els[i+7] = new MyHelixElement(Color.gray, 48); els[i+8] = new MyHelixElement(Color.blue, 38); els[i+9] = new MyHelixElement(Color.pink, 30); } // add listeners for the mouse addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { currentX = e.getX(); currentY = e.getY(); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { updateView(e); } }); } // use the mouse events to update the two values that control the Helix, // namely the degreeOffset (how much it has rotated from it's default // orientation) and the diagOffset (how far it has slide up and down // the diagonal). public void updateView(MouseEvent e) { int newX = e.getX(); int newY = e.getY(); // save some cycles if ( (currentX != newX) || (currentY != newY) ) { diagOffset -= (double)(newY - currentY); // don't let them scroll up past the original starting place if (diagOffset < 0.0) diagOffset = 0.0; // don't let them scroll everything off the screen if (diagOffset > (pixelsPerElem * Array.getLength(els))) diagOffset = (pixelsPerElem * Array.getLength(els)); // rotation of the helix degreeOffset -= (double)(newX - currentX); // wrap around 360 both ways. while (degreeOffset < 0.0) degreeOffset += 360.0; while (degreeOffset > 360.0) degreeOffset -= 360.0; repaint(); currentX = newX; currentY = newY; } } //////////////////////////////////////// // Some values that control the helix display. // space between elements on helix. double degreePerElement = 67.0; // distance down the diagonal traveled by one loop of the helix double pixelsPerLoop = 100.0; // width of the helix double pixelsWide = 50.0; // max width of image (used to guess how many elements above the screen // to draw so their bottoms will be drawn on the top of the screen) double imageWidth = 100.0; // the offset we change by the X mouse motion double degreeOffset = 0.0; // the offset we change by the Y mouse motion double diagOffset = 0.0; // computed: number of pixels per element. Must recompute when either // of the other values changes. double pixelsPerElem = (degreePerElement/360.0) * pixelsPerLoop; // use paintComponent to create our own graphics public void paintComponent(Graphics g) { super.paintComponent(g); // get the width and height that accounts for the border Insets insets = getInsets(); int currentWidth = getWidth () - insets.left - insets.right; int currentHeight = getHeight () - insets.top - insets.bottom; // need the length of the diagonal double currentDiag = Math.sqrt((double)currentHeight*currentHeight + (double)currentWidth*currentWidth); // temp var int i; // "first" element to draw, based on the current location. The // "origin" is imageWidth in from upper right, and we want to start // drawing the ones imageWidth off the screen so they slide off screen // completely before not being drawn int first = (int)((diagOffset - imageWidth*2.0)/pixelsPerElem); // the number of elements to draw so that we don't go too far past // the bottom of the screen int number = (int)((currentDiag + imageWidth)/pixelsPerElem); // make sure we don't try to grab more than are there! if ((first + number) > Array.getLength(els)) { number = Array.getLength(els) - first; if (number <= 0) // scrolled off screen, don't need to draw! // Shouldn't happen. :-) return; } // make sure we have enough space in the array for the ones we // are drawing. if (Array.getLength(sortEls) < number) sortEls = new MyHelixElement[number]; // two cases: if first is negative, it means that the first ones we // draw (starting above the screen) are actually non-existant. This // is where things start. if first is positive, we have more than // enough above the screen to clip some off. if (first<0) { // set the first ones to null to signify no element is there for (i=0; i<(-first); i++) sortEls[i]=null; // copy the real ones from els[] for (i=0; i<(first+number); i++) sortEls[i-first] = els[i]; } else { // copy the right number of elements for (i=first; i<(first+number); i++) sortEls[i-first] = els[i]; } // computes the current x,y,z values for each of the elements in // sortEls. First, figure the cos and sin of the angle of // the diagonal line, using high school trig! double cosDiag = currentHeight/currentDiag; double sinDiag = currentWidth/currentDiag; computeXYZ(g, first, number, cosDiag, sinDiag); // now, translate the origin to the correct place for (i = 0; i 360.0) angle -= 360.0; double hY = (first * pixelsPerElem) - diagOffset; // now, compute the position for each one for (i = 0; i 360.0) angle -= 360.0; // increment the Y by the number of pixels per element hY += pixelsPerElem; } } public static void main(String s[]) { Helix panel = new Helix(); frame = new JFrame("Helix"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); frame.getContentPane().add("Center", panel); frame.pack(); frame.setSize(640,500); frame.setVisible(true); } }