package stdlib;

/*************************************************************************
 *  StdDraw3D.java
 *
 *************************************************************************/

// Imports from java.
//import java.io.*;
//import java.net.*;
//import java.awt.*;
//import java.util.*;
//import javax.swing.*;
//import javax.swing.event.*;
//import java.text.*;
//import java.awt.geom.*;
//import java.awt.event.*;
//import java.awt.image.*;
//import javax.imageio.ImageIO;

//3D Classes
//import com.sun.j3d.utils.universe.*;
//import javax.media.j3d.*;
//import com.sun.j3d.utils.image.*;
//import javax.vecmath.*;
//import com.sun.j3d.utils.geometry.*;
//import com.sun.j3d.utils.universe.ViewingPlatform;
//
//import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;


/**
 *  Info for javadoc intro here.
 */
public final class StdDraw3D //implements 
//MouseListener, MouseMotionListener, MouseWheelListener, 
//KeyListener, ActionListener, ChangeListener, ComponentListener, WindowFocusListener
{
//
//    /* ***************************************************************
//     *                      Global Variables                         *
//     *****************************************************************/
//
//    /* Public constant values. */
//    //-------------------------------------------------------------------------
//
//    // Preset colors.
//    public static final Color BLACK      = Color.BLACK;
//    public static final Color BLUE       = Color.BLUE;
//    public static final Color CYAN       = Color.CYAN;
//    public static final Color DARK_GRAY  = Color.DARK_GRAY;
//    public static final Color GRAY       = Color.GRAY;
//    public static final Color GREEN      = Color.GREEN;
//    public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
//    public static final Color MAGENTA    = Color.MAGENTA;
//    public static final Color ORANGE     = Color.ORANGE;
//    public static final Color PINK       = Color.PINK;
//    public static final Color RED        = Color.RED;
//    public static final Color WHITE      = Color.WHITE;
//    public static final Color YELLOW     = Color.YELLOW;
//
//    // Font styles.
//    public static final int BOLD   = Font.BOLD;
//    public static final int ITALIC = Font.ITALIC;
//    public static final int PLAIN  = Font.PLAIN;
//
//    // Camera modes.
//    public static final int ORBIT_MODE = 0;
//    public static final int FPS_MODE   = 1;
//    public static final int AIRPLANE_MODE = 2;
//    public static final int LOOK_MODE = 3;
//    public static final int FIXED_MODE = 4;
//    public static final int IMMERSIVE_MODE = 5;
//
//    /* Global variables. */
//    //-------------------------------------------------------------------------
//
//    // GUI Components
//    private static JFrame frame;
//    private static Panel canvasPanel;
//    private static JMenuBar menuBar;
//    private static JMenu fileMenu, cameraMenu, graphicsMenu;
//    private static JMenuItem loadButton, saveButton, save3DButton, quitButton;
//    private static JSpinner fovSpinner;
//    private static JRadioButtonMenuItem 
//    orbitModeButton, fpsModeButton, airplaneModeButton, lookModeButton, fixedModeButton;
//    private static JRadioButtonMenuItem perspectiveButton, parallelButton;
//    private static JCheckBoxMenuItem antiAliasingButton;
//    private static JSpinner numDivSpinner;
//    private static JCheckBox infoCheckBox;
//
//    // Scene groups.
//    private static SimpleUniverse universe;
//    private static BranchGroup rootGroup, lightGroup, soundGroup, fogGroup, appearanceGroup;
//    private static BranchGroup onscreenGroup, offscreenGroup;
//    private static OrbitBehavior orbit;
//    private static Background background;
//    private static Group bgGroup;
//    private static View view;
//
//    // Drawing canvas.
//    private static Canvas3D canvas;
//
//    // Camera object
//    private static Camera camera;
//
//    // Buffered Images for 2D drawing
//    private static BufferedImage offscreenImage, onscreenImage;
//    private static BufferedImage infoImage;
//
//    // Robot for mouse control in FPS_MODE
//    private static Robot robot;
//
//    // Canvas dimensions.
//    private static int width;
//    private static int height;
//    private static double aspectRatio;
//
//    // Camera mode.
//    private static int cameraMode;
//
//    // Center of orbit
//    private static Point3d orbitCenter;
//
//    // Coordinate bounds.
//    private static double min, max, zoom;
//
//    // Pen properties.
//    private static Color penColor;
//    private static float penRadius;
//    private static Font font;
//
//    // Keeps track of screen clearing.
//    private static boolean  clear3D;
//    private static boolean clearOverlay;
//    private static boolean infoDisplay;
//
//    // Number of triangles per shape.
//    private static int numDivisions;
//
//    // Mouse states.
//    private static boolean mouse1;
//    private static boolean mouse2;
//    private static boolean mouse3;
//    private static double mouseX;
//    private static double mouseY;
//
//    // Keyboard states.
//    private static TreeSet<Integer> keysDown = new TreeSet<Integer>();
//    private static LinkedList<Character> keysTyped = new LinkedList<Character>();
//
//    // For event synchronization.
//    private static Object mouseLock = new Object();
//    private static Object keyLock = new Object();
//
//    // Helper to see when initialization complete
//    private static boolean initialized = false;
//    private static boolean fullscreen = false;
//    private static boolean immersive = false;
//
//    /* Final variables for default values. */
//    //-------------------------------------------------------------------------
//
//    // Default square canvas dimension in pixels.
//    private static final int DEFAULT_SIZE = 600;
//
//    // Default boundaries of canvas scale.
//    private static final double DEFAULT_MIN = -1.0;
//    private static final double DEFAULT_MAX =  1.0;
//
//    // Default camera mode
//    private static final int    DEFAULT_CAMERA_MODE = ORBIT_MODE;
//
//    // Default field of vision for perspective projection.
//    private static final double DEFAULT_FOV = 1.0;
//    private static final int    DEFAULT_NUM_DIVISIONS = 100;
//
//    // Default clipping distances for rendering.
//    private static final double DEFAULT_FRONT_CLIP = 0.01;
//    private static final double DEFAULT_BACK_CLIP  = 10;
//
//    // Default pen settings.
//    private static final Font  DEFAULT_FONT       = new Font("Arial", Font.PLAIN, 16);
//    private static final float DEFAULT_PEN_RADIUS = 1.0f;
//    private static final Color DEFAULT_PEN_COLOR  = StdDraw3D.WHITE;
//
//    // Scales the size of Text3D.
//    private static final double TEXT3D_SHRINK_FACTOR = 0.005;
//    private static final double TEXT3D_DEPTH         = 1.5;
//
//    // Default shape flags.
//    private static final int PRIMFLAGS = 
//        Primitive.GENERATE_NORMALS + Primitive.GENERATE_TEXTURE_COORDS;
//
//    // Infinite bounding sphere.
//    private static final BoundingSphere INFINITE_BOUNDS = 
//        new BoundingSphere(new Point3d(0.0,0.0,0.0), 1e100);
//
//    // Axis vectors.
//    private static final Vector3D xAxis = new Vector3D(1, 0, 0);
//    private static final Vector3D yAxis = new Vector3D(0, 1, 0);
//    private static final Vector3D zAxis = new Vector3D(0, 0, 1);
//
//    /* Housekeeping. */
//    //-------------------------------------------------------------------------
//
//    // Singleton for callbacks - avoids generation of extra .class files.
//    private static StdDraw3D std = new StdDraw3D();
//
//    // Blank constructor.
//    private StdDraw3D () { }
//
//    // Static initializer.
//    static { 
//        //System.setProperty("j3d.rend", "ogl");
//        System.setProperty("j3d.audiodevice", "com.sun.j3d.audioengines.javasound.JavaSoundMixer");
//        setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); 
//    }
//
//    /* ***************************************************************
//     *                      Initialization Methods                   *
//     *****************************************************************/
//
//    /**
//     * Initializes the 3D engine.
//     */
//    private static void initialize () {
//
//        numDivisions = DEFAULT_NUM_DIVISIONS;
//
//        onscreenImage  = createBufferedImage();
//        offscreenImage = createBufferedImage();
//        infoImage      = createBufferedImage();
//
//        initializeCanvas();
//
//        if (frame != null) frame.setVisible(false);
//        frame = new JFrame();
//        frame.setVisible(false);
//        frame.setResizable(fullscreen);
//        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.setTitle("Standard Draw 3D");
//        frame.add(canvasPanel);
//        frame.setJMenuBar(createMenuBar()); 
//        frame.addComponentListener(std);
//        frame.addWindowFocusListener(std);
//        //frame.getContentPane().setCursor(new Cursor(Cursor.MOVE_CURSOR));
//        frame.pack();
//
//        rootGroup = createBranchGroup();
//        lightGroup = createBranchGroup(); 
//        bgGroup = createBranchGroup();
//        soundGroup = createBranchGroup();
//        fogGroup = createBranchGroup();
//        appearanceGroup = createBranchGroup();
//
//        onscreenGroup  = createBranchGroup();
//        offscreenGroup = createBranchGroup();
//
//        rootGroup.addChild(onscreenGroup);
//        rootGroup.addChild(lightGroup);
//        rootGroup.addChild(bgGroup);
//        rootGroup.addChild(soundGroup);
//        rootGroup.addChild(fogGroup);
//        rootGroup.addChild(appearanceGroup);
//
//        universe = new SimpleUniverse(canvas, 2);
//        universe.addBranchGraph(rootGroup);
//
//        setDefaultLight();
//        setBackground(BLACK);
//
//        Viewer viewer = universe.getViewer();
//        viewer.createAudioDevice();
//
//        view = viewer.getView();
//        view.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
//        view.setScreenScalePolicy(View.SCALE_EXPLICIT);
//        view.setLocalEyeLightingEnable(true);
//        setAntiAliasing(false);
//
//        //view.setMinimumFrameCycleTime(long minimumTime);
//
//        ViewingPlatform viewingPlatform = universe.getViewingPlatform();
//        viewingPlatform.setNominalViewingTransform();
//
//        orbit = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL^OrbitBehavior.STOP_ZOOM);
//        BoundingSphere bounds = INFINITE_BOUNDS;
//        orbit.setMinRadius(0);
//        orbit.setSchedulingBounds(bounds);
//        setOrbitCenter(new Point3d(0, 0, 0));
//
//        viewingPlatform.setViewPlatformBehavior(orbit);
//        TransformGroup cameraTG = viewingPlatform.getViewPlatformTransform();
//
//        Transform3D cameraTrans = new Transform3D();
//        cameraTG.getTransform(cameraTrans);
//
//        viewingPlatform.detach();
//        cameraTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
//        cameraTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
//        camera = new Camera(cameraTG);
//        universe.addBranchGraph(viewingPlatform);
//
//        setPerspectiveProjection();
//        setCameraMode();
//        setPenColor();
//        setPenRadius();
//        setFont();
//        setScale();
//        setInfoDisplay(true);
//
//        try {
//            robot = new Robot();
//        } catch (AWTException ae) { ae.printStackTrace(); }
//
//        frame.setVisible(true);
//        frame.toFront();
//        frame.setState(Frame.NORMAL);
//        initialized = true;
//
//    }
//
//    /**
//     * Adds a Canvas3D to the given Panel p.
//     */
//    private static void initializeCanvas () {
//
//        Panel p = new Panel();
//
//        GridBagLayout gl = new GridBagLayout();
//        GridBagConstraints gbc = new GridBagConstraints();
//
//        p.setLayout(gl);
//        gbc.gridx = 0;
//        gbc.gridy = 0;
//        gbc.gridwidth = 5;
//        gbc.gridheight = 5;
//
//        GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
//
//        canvas = new Canvas3D(config) {
//
//            public void preRender () {
//                if (camera.pair != null) {
//                    camera.setPosition(camera.pair.getPosition());
//                    camera.setOrientation(camera.pair.getOrientation());
//                }
//            }
//
//            public void postRender () {
//                J3DGraphics2D graphics = this.getGraphics2D();
//
//                graphics.drawRenderedImage(onscreenImage, new AffineTransform());
//                if (infoDisplay) {
//                    graphics.drawRenderedImage(infoImage, new AffineTransform());
//                }
//
//                graphics.flush(false);
//            }
//        };
//
//        canvas.addKeyListener(std);
//        canvas.addMouseListener(std);
//        canvas.addMouseMotionListener(std);
//        canvas.addMouseWheelListener(std);
//
//        canvas.setSize(width, height);
//        p.add(canvas, gbc);
//
//        canvasPanel = p;
//    }
//
//    /**
//     * Creates a menu bar as a basic GUI.
//     * 
//     * @return The created JMenuBar.
//     */
//    private static JMenuBar createMenuBar () {
//
//        menuBar = new JMenuBar();
//
//        fileMenu = new JMenu("File");
//        menuBar.add(fileMenu);
//
//        loadButton = new JMenuItem(" Load 3D Model..  ");
//        loadButton.addActionListener(std);
//        loadButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//        fileMenu.add(loadButton);
//
//        saveButton = new JMenuItem(" Save Image...  ");
//        saveButton.addActionListener(std);
//        saveButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//        fileMenu.add(saveButton);
//
//        save3DButton = new JMenuItem(" Export 3D Scene...  ");
//        save3DButton.addActionListener(std);
//        save3DButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//        //fileMenu.add(save3DButton);
//
//        fileMenu.addSeparator();
//
//        quitButton = new JMenuItem(" Quit...   ");
//        quitButton.addActionListener(std);
//        quitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//        fileMenu.add(quitButton);
//
//        cameraMenu = new JMenu("Camera");
//        menuBar.add(cameraMenu);
//
//        JLabel cameraLabel = new JLabel("Camera Mode");
//        cameraLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
//        cameraLabel.setForeground(GRAY);
//        cameraMenu.add(cameraLabel);
//        cameraMenu.addSeparator();
//
//        ButtonGroup cameraButtonGroup = new ButtonGroup();
//
//        orbitModeButton 
//        = new JRadioButtonMenuItem("Orbit Mode");
//        orbitModeButton.setSelected(true);
//        cameraButtonGroup.add(orbitModeButton);
//        cameraMenu.add(orbitModeButton);
//        orbitModeButton.addActionListener(std);
//        orbitModeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//
//        fpsModeButton 
//        = new JRadioButtonMenuItem("First-Person Mode");
//        cameraButtonGroup.add(fpsModeButton);
//        cameraMenu.add(fpsModeButton);
//        fpsModeButton.addActionListener(std);
//        fpsModeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//
//        airplaneModeButton 
//        = new JRadioButtonMenuItem("Airplane Mode");
//        cameraButtonGroup.add(airplaneModeButton);
//        cameraMenu.add(airplaneModeButton);
//        airplaneModeButton.addActionListener(std);
//        airplaneModeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//
//        lookModeButton 
//        = new JRadioButtonMenuItem("Look Mode");
//        cameraButtonGroup.add(lookModeButton);
//        cameraMenu.add(lookModeButton);
//        lookModeButton.addActionListener(std);
//        lookModeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//
//        fixedModeButton 
//        = new JRadioButtonMenuItem("Fixed Mode");
//        cameraButtonGroup.add(fixedModeButton);
//        cameraMenu.add(fixedModeButton);
//        fixedModeButton.addActionListener(std);
//        fixedModeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_5,
//                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
//
//        cameraMenu.addSeparator();
//        JLabel projectionLabel = new JLabel("Projection Mode");
//        projectionLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
//        projectionLabel.setForeground(GRAY);
//        cameraMenu.add(projectionLabel);
//        cameraMenu.addSeparator();
//
//        SpinnerNumberModel snm = new SpinnerNumberModel(DEFAULT_FOV, 0.5, 3.0, 0.05);
//        fovSpinner = new JSpinner(snm);
//        JPanel fovPanel = new JPanel();
//        fovPanel.setLayout(new BoxLayout(fovPanel, BoxLayout.X_AXIS));
//        JLabel fovLabel = new JLabel("Field of View:");
//        fovPanel.add(javax.swing.Box.createRigidArea(new Dimension(30, 5)));
//        fovPanel.add(fovLabel);
//        fovPanel.add(javax.swing.Box.createRigidArea(new Dimension(10, 5)));
//        fovPanel.add(fovSpinner);
//
//        final ButtonGroup projectionButtons = new ButtonGroup();
//        perspectiveButton = new JRadioButtonMenuItem("Perspective Projection");
//        parallelButton = new JRadioButtonMenuItem("Parallel Projection");
//
//        fovSpinner.addChangeListener(std);
//
//        perspectiveButton.addActionListener(std);
//
//        parallelButton.addActionListener(std);
//
//        cameraMenu.add(parallelButton);
//        cameraMenu.add(perspectiveButton);
//        cameraMenu.add(fovPanel);
//
//        projectionButtons.add(parallelButton);
//        projectionButtons.add(perspectiveButton);
//        perspectiveButton.setSelected(true);
//
//        graphicsMenu = new JMenu("Graphics");
//        menuBar.add(graphicsMenu);
//
//        JLabel graphicsLabel = new JLabel("Polygon Count");
//        graphicsLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
//        graphicsLabel.setForeground(GRAY);
//        graphicsMenu.add(graphicsLabel);
//        graphicsMenu.addSeparator();
//
//        SpinnerNumberModel snm2 = 
//            new SpinnerNumberModel(DEFAULT_NUM_DIVISIONS, 4, 4000, 5);
//        numDivSpinner = new JSpinner(snm2);
//
//        JPanel numDivPanel = new JPanel();
//        numDivPanel.setLayout(new BoxLayout(numDivPanel, BoxLayout.X_AXIS));
//        JLabel numDivLabel = new JLabel("Triangles:");
//        numDivPanel.add(javax.swing.Box.createRigidArea(new Dimension(5, 5)));
//        numDivPanel.add(numDivLabel);
//        numDivPanel.add(javax.swing.Box.createRigidArea(new Dimension(15, 5)));
//        numDivPanel.add(numDivSpinner);
//        graphicsMenu.add(numDivPanel);
//
//        numDivSpinner.addChangeListener(std);
//
//        graphicsMenu.addSeparator();
//        JLabel graphicsLabel2 = new JLabel("Advanced Rendering");
//        graphicsLabel2.setAlignmentX(Component.CENTER_ALIGNMENT);
//        graphicsLabel2.setForeground(GRAY);
//        graphicsMenu.add(graphicsLabel2);
//        graphicsMenu.addSeparator();
//
//        antiAliasingButton = new JCheckBoxMenuItem("Enable Anti-Aliasing");
//        antiAliasingButton.setSelected(false);
//        antiAliasingButton.addActionListener(std);
//
//        graphicsMenu.add(antiAliasingButton);
//
//        infoCheckBox = new JCheckBox("Show Info Display");
//        infoCheckBox.setFocusable(false);
//        infoCheckBox.addActionListener(std);
//        menuBar.add(javax.swing.Box.createRigidArea(new Dimension(50, 5)));
//        menuBar.add(infoCheckBox);
//
//        return menuBar;
//    }
//
//    @Override
//	public void stateChanged(ChangeEvent e) {
//        Object source = e.getSource();
//
//        if (source == numDivSpinner)
//            numDivisions = (Integer)numDivSpinner.getValue();
//        if (source == fovSpinner) {
//            setPerspectiveProjection((Double)fovSpinner.getValue());
//            perspectiveButton.setSelected(true);
//        }
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void actionPerformed (ActionEvent e) {
//        Object source = e.getSource();
//
//        if (source == saveButton)      
//            saveAction();
//        else if (source == loadButton)
//            loadAction();
//        else if (source == save3DButton)    
//            save3DAction();
//        else if (source == quitButton)      
//            quitAction();
//        else if (source == orbitModeButton) 
//            setCameraMode(ORBIT_MODE);
//        else if (source == fpsModeButton)   
//            setCameraMode(FPS_MODE);
//        else if (source == airplaneModeButton)
//            setCameraMode(AIRPLANE_MODE);
//        else if (source == lookModeButton)  
//            setCameraMode(LOOK_MODE);
//        else if (source == fixedModeButton) 
//            setCameraMode(FIXED_MODE);
//        else if (source == perspectiveButton) 
//            setPerspectiveProjection((Double)fovSpinner.getValue());
//        else if (source == parallelButton) 
//            setParallelProjection();
//        else if (source == antiAliasingButton)
//            setAntiAliasing(antiAliasingButton.isSelected());
//        else if (source == infoCheckBox)
//            setInfoDisplay(infoCheckBox.isSelected());
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void componentHidden(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void componentMoved(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void componentShown(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void componentResized(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void windowGainedFocus(WindowEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void windowLostFocus(WindowEvent e) { keysDown = new TreeSet<Integer>(); }
//
//    /**
//     * Creates a blank BranchGroup with the proper capabilities.
//     */
//    private static BranchGroup createBranchGroup () {
//
//        BranchGroup bg = new BranchGroup();
//        bg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
//        bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
//        bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
//        bg.setCapability(BranchGroup.ALLOW_DETACH);
//        bg.setPickable(false);
//        bg.setCollidable(false);
//        return bg;
//    }
//
//    /**
//     * Creates a blank TransformGroup with the proper capabilities.
//     */
//    private static TransformGroup createTransformGroup () {
//
//        TransformGroup tg = new TransformGroup();
//        tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
//        tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
//        tg.setPickable(false);
//        tg.setCollidable(false);
//        return tg;
//    }
//
//    /**
//     * Creates a blank Background with the proper capabilities.
//     * 
//     * @return The created Background.
//     */
//    private static Background createBackground () {
//
//        Background background = new Background();
//        background.setCapability(Background.ALLOW_COLOR_WRITE);
//        background.setCapability(Background.ALLOW_IMAGE_WRITE);
//        background.setCapability(Background.ALLOW_GEOMETRY_WRITE);
//        background.setApplicationBounds(INFINITE_BOUNDS);
//        return background;
//    }
//
//    /**
//     * Creates a texture from the given filename.
//     * 
//     * @param imageURL Image file for creating texture.
//     * @return The created Texture.
//     * @throws RuntimeException if the file could not be read.
//     */
//    private static Texture createTexture (String imageURL) {
//
//        TextureLoader loader;
//        try {
//            loader = new TextureLoader(imageURL, "RGBA", TextureLoader.Y_UP, new Container());
//        } catch (Exception e) {
//            throw new RuntimeException ("Could not read from the file '" + imageURL + "'");
//        }
//
//        Texture texture = loader.getTexture();
//        texture.setBoundaryModeS(Texture.WRAP);
//        texture.setBoundaryModeT(Texture.WRAP);
//        texture.setBoundaryColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.0f ) );
//
//        return texture;
//    }
//
//    private static Appearance createBlankAppearance () {
//        Appearance ap = new Appearance();
//        ap.setCapability(Appearance.ALLOW_MATERIAL_READ);
//        ap.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
//        ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
//        ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
//        return ap;
//    }
//
//    /**
//     * Creates an Appearance.
//     * 
//     * @param imageURL Wraps a texture around from this image file.
//     * @param fill If true, fills in faces. If false, outlines.
//     * @return The created appearance.
//     */
//    private static Appearance createAppearance (String imageURL, boolean fill) {  
//
//        Appearance ap = createBlankAppearance();
//
//        PolygonAttributes pa = new PolygonAttributes();
//        if (!fill) pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
//        pa.setCullFace(PolygonAttributes.CULL_NONE);
//        ap.setPolygonAttributes(pa);
//
//        if (imageURL != null) {
//
//            Texture texture = createTexture(imageURL);
//            TextureAttributes texAttr = new TextureAttributes();
//            texAttr.setTextureMode(TextureAttributes.REPLACE);
//
//            ap.setTexture(texture);
//            ap.setTextureAttributes(texAttr);
//        }
//
//        Color3f col = new Color3f(penColor);
//        Color3f black = new Color3f(0, 0, 0);
//        Color3f specular = new Color3f(GRAY);
//
//        // Material properties
//        Material material = new Material(col, black, col, specular, 64);
//        material.setCapability(Material.ALLOW_COMPONENT_READ);
//        material.setCapability(Material.ALLOW_COMPONENT_WRITE);
//        material.setLightingEnable(true);
//        ap.setMaterial(material);
//
//        // Transparecy properties
//        float alpha = ((float)penColor.getAlpha()) / 255;
//        if (alpha < 1.0) {
//            TransparencyAttributes t = new TransparencyAttributes();
//            t.setTransparencyMode(TransparencyAttributes.BLENDED);
//            t.setTransparency(1 - alpha);
//            ap.setTransparencyAttributes(t);
//        }
//
//        LineAttributes la = new LineAttributes();
//        la.setLineWidth(penRadius);
//        la.setLineAntialiasingEnable(view.getSceneAntialiasingEnable());
//
//        PointAttributes poa = new PointAttributes();
//        poa.setPointAntialiasingEnable(view.getSceneAntialiasingEnable());
//
//        ColoringAttributes ca = new ColoringAttributes();
//        ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
//        ca.setColor(col);
//
//        ap.setLineAttributes(la);
//        ap.setPointAttributes(poa);
//        ap.setColoringAttributes(ca);
//
//        return ap;
//    }
//
//    // FIX THIS check that adding specular didn't mess up custom shapes (lines, triangles, points)
//    private static Appearance createCustomAppearance (boolean fill) {
//        Appearance ap = createBlankAppearance();
//
//        PolygonAttributes pa = new PolygonAttributes();
//        if (!fill) pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
//        pa.setCullFace(PolygonAttributes.CULL_NONE);
//
//        LineAttributes la = new LineAttributes();
//        la.setLineWidth(penRadius);
//        la.setLineAntialiasingEnable(view.getSceneAntialiasingEnable());
//
//        PointAttributes poa = new PointAttributes();
//        poa.setPointAntialiasingEnable(view.getSceneAntialiasingEnable());
//
//        ap.setPolygonAttributes(pa);
//        ap.setLineAttributes(la);
//        ap.setPointAttributes(poa);
//
//        Color3f col = new Color3f(penColor);
//        Color3f black = new Color3f(0, 0, 0);
//        Color3f specular = new Color3f(GRAY);
//
//        // Material properties
//        Material material = new Material(col, black, col, specular, 64);
//        material.setCapability(Material.ALLOW_COMPONENT_READ);
//        material.setCapability(Material.ALLOW_COMPONENT_WRITE);
//        material.setLightingEnable(true);
//        ap.setMaterial(material);
//
//        return ap;
//    }
//
//    private static Shape3D createShape3D (Geometry geom) {
//        Shape3D shape = new Shape3D(geom);
//        shape.setPickable(false);
//        shape.setCollidable(false);
//        shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
//        shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
//        shape.setCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_READ);
//        shape.setCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE);
//
//        return shape;
//    }
//
//    /**
//     * Creates a blank BufferedImage.
//     * 
//     * @return The created BufferedImage.
//     */
//    private static BufferedImage createBufferedImage () {
//        return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
//    }
//
//    /** Converts a Vector3D to a Vector3d. */
//    private static Vector3d createVector3d (Vector3D v) {
//        return new Vector3d(v.x, v.y, v.z);
//    }
//
//    /** Converts three double scalars to a Vector3f. **/
//    private static Vector3f createVector3f (double x, double y, double z) {
//        return new Vector3f((float)x, (float)y, (float)z);
//    }
//
//    /** Converts a Vector3D to a Vector3f. */
//    private static Vector3f createVector3f (Vector3D v) {
//        return createVector3f(v.x, v.y, v.z);
//    }
//
//    /** Converts a Vector3D to a Point3f. */
//    private static Point3f createPoint3f (Vector3D v) {
//        return createPoint3f(v.x, v.y, v.z);
//    }
//
//    /** Converts three double scalars to a Point3f. */
//    private static Point3f createPoint3f (double x, double y, double z) {
//        return new Point3f((float)x, (float)y, (float)z);
//    }
//
//    /** 
//     * Creates a scanner from the filename or website name.
//     */
//    private static Scanner createScanner (String s) {
//
//        Scanner scanner;
//        String charsetName = "ISO-8859-1";
//        java.util.Locale usLocale = new java.util.Locale("en", "US");
//        try {
//            // first try to read file from local file system
//            File file = new File(s);
//            if (file.exists()) {
//                scanner = new Scanner(file, charsetName);
//                scanner.useLocale(usLocale);
//                return scanner;
//            }
//
//            // next try for website
//            URL url = new URL(s);
//
//            URLConnection site = url.openConnection();
//            InputStream is     = site.getInputStream();
//            scanner            = new Scanner(new BufferedInputStream(is), charsetName);
//            scanner.useLocale(usLocale);
//            return scanner;
//        }
//        catch (IOException ioe) {
//            System.err.println("Could not open " + s + ".");
//            return null;
//        }
//    }
//
//    /* ***************************************************************
//     *                  Scaling and Screen Methods                   *
//     *****************************************************************/
//
//    /**
//     * Sets the window size to w-by-h pixels.
//     *
//     * @param w The width as a number of pixels.
//     * @param h The height as a number of pixels.
//     * @throws a RuntimeException if the width or height is 0 or negative.
//     */
//    public static void setCanvasSize (int w, int h) {
//
//        setCanvasSize(w, h, false);
//    }
//
//    private static void setCanvasSize (int w, int h, boolean fs) {
//
//        fullscreen = fs;
//
//        if (w < 1 || h < 1) throw new RuntimeException("Dimensions must be positive integers!");
//        width = w;
//        height = h;
//
//        aspectRatio = (double)width / (double)height;
//        initialize();
//    }
//
//    /**
//     * Sets the default scale for all three dimensions.
//     */
//    public static void setScale () { setScale(DEFAULT_MIN, DEFAULT_MAX); }
//
//    /**
//     * Sets the scale for all three dimensions.
//     * 
//     * @param minimum The minimum value of each scale.
//     * @param maximum The maximum value of each scale.
//     */
//    public static void setScale (double minimum, double maximum) {
//
//        min = minimum;
//        max = maximum;
//        zoom = (max - min) / 2;
//        double center = min + zoom;
//
//        double nominalDist = camera.getPosition().z;
//        camera.setPosition(center, center, zoom * nominalDist);
//
//        double orbitScale = 0.5 * zoom;
//
//        orbit.setZoomFactor(orbitScale);
//        orbit.setTransFactors(orbitScale, orbitScale);
//
//        setOrbitCenter(new Point3d(center, center, center));
//
//        view.setFrontClipDistance(DEFAULT_FRONT_CLIP * zoom);
//        view.setBackClipDistance(DEFAULT_BACK_CLIP * zoom);
//    }
//
//    /**
//     * Scales the given x-coordinate from user coordinates into 2D pixel coordinates.
//     */
//    private static float scaleX (double x) { 
//
//        double scale = 1;
//        if (width > height) scale = 1 / aspectRatio;
//
//        return (float)(width  * (x * scale - min) / (2 * zoom)); 
//    }
//
//    /**
//     * Scales the given y-coordinate from user coordinates into 2D pixel coordinates.
//     */
//    private static float scaleY (double y) { 
//
//        double scale = 1;
//        if (height > width) scale = aspectRatio;
//
//        return (float)(height * (max - y * scale) / (2 * zoom));
//    }
//
//    /**
//     * Scales the given width from user coordinates into 2D pixel coordinates.
//     */
//    private static double factorX (double w) { 
//
//        double scaleDist = width;
//        if (width > height) scaleDist = height;
//
//        return scaleDist  * (w / (2 * zoom)); 
//    }
//
//    /**
//     * Scales the given height from user coordinates into 2D pixel coordinates.
//     */
//    private static double factorY (double h) { 
//
//        double scaleDist = height;
//        if (height > width) scaleDist = width;
//
//        return scaleDist * (h / (2 * zoom)); 
//    }
//
//    /**
//     * Scales the given x-coordinate from 2D pixel coordinates into user coordinates.
//     */
//    private static double unscaleX (double xs) {
//
//        double scale = 1;
//        if (width > height) scale = 1 / aspectRatio;
//
//        return (xs * (2 * zoom) / width + min) / scale;
//    }
//
//    /**
//     * Scales the given y-coordinate from 2D pixel coordinates into user coordinates.
//     */
//    private static double unscaleY (double ys) {
//
//        double scale = 1;
//        if (height > width) scale = aspectRatio;
//        //System.out.println("unscaleY scale = " + scale);
//        return (max - ys * (2 * zoom) / height) / scale;
//    }
//
//    /* ***************************************************************
//     *                  Pen Properties Methods                       *
//     *****************************************************************/
//
//    /** 
//     * Sets the pen color to the given color and transparency. 
//     * 
//     * @param col The given opaque color.
//     * @param alpha The transparency value (0-255).
//     */
//    public static void setPenColor (Color col, int alpha) {
//        setPenColor(new Color(col.getRed(), col.getGreen(), col.getBlue(), alpha));
//    }
//
//    /**
//     * Sets the pen color to the default.
//     */
//    public static void  setPenColor () { penColor = DEFAULT_PEN_COLOR; }
//
//    /**
//     * Sets the pen color to the given color.
//     * 
//     * @param col The given color.
//     */
//    public static void  setPenColor (Color col) { penColor = col; }
//
//    public static void setPenColor (int r, int g, int b) {
//        penColor = new Color(r, g, b);    
//    }
//
//    /**
//     * Returns the current pen color.
//     * 
//     * @return The current pen color.
//     */
//    public static Color getPenColor () { return penColor; }
//
//    /**
//     * Sets the pen radius to the default value.
//     */
//    public static void setPenRadius () { penRadius = DEFAULT_PEN_RADIUS; }
//
//    /**
//     * Sets the pen radius to the given value.
//     * 
//     * @param w The pen radius.
//     */
//    public static void  setPenRadius (double w) { penRadius = (float)w; }
//
//    /**
//     * Gets the current pen radius.
//     * 
//     * @return The current pen radius.
//     */
//    public static float getpenRadius () { return penRadius; }
//
//    /**
//     * Sets the default font.
//     */
//    public static void setFont () { font = DEFAULT_FONT; }
//
//    /**
//     * Sets the font to draw text with.
//     * 
//     * @param f The font to set.
//     */
//    public static void setFont (Font f) { font = f; }
//
//    /**
//     * Sets the font to draw text with. The font name and point
//     * size are given as parameters.
//     * 
//     * @param name  The name of the font.
//     * @param size  The point size of the font.
//     */
//    public static void setFont (String name, int size) {
//        setFont(new Font(name, PLAIN, size));
//    }
//
//    /**
//     * Sets the font to draw text with. The font name, style, and 
//     * size are given as parameters. The style can be a bitwise 
//     * combination of StdDraw3D.PLAIN, StdDraw3D.ITALIC, and
//     * StdDraw3D.BOLD.
//     * 
//     * @param name  The name of the font.
//     * @param style The style of the font.
//     * @param size  The point size of the font.
//     */
//    public static void setFont (String name, int style, int size) {
//        setFont(new Font(name, style, size));
//    }
//
//    /**
//     * Gets the current drawing Font.
//     * 
//     * @return The current Font.
//     */
//    public static Font getFont () { return font; }
//
//    public static void setInfoDisplay (boolean enabled) {
//        infoDisplay = enabled;
//        infoCheckBox.setSelected(enabled);
//        camera.move(0, 0, 0);
//        infoDisplay();
//    }
//
//    /* ***************************************************************
//     *             View Properties Methods                           *
//     *****************************************************************/
//
//    public static void fullscreen () {
//
//        frame.setResizable(true);
//        frame.setExtendedState(Frame.MAXIMIZED_BOTH);
//        int w = frame.getSize().width;
//        int h = frame.getSize().height;
//
//        //int w = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
//        //int h = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
//
//        int borderY = frame.getInsets().top + frame.getInsets().bottom;
//        int borderX = frame.getInsets().left + frame.getInsets().right;
//
//        setCanvasSize(w - borderX, h - borderY - menuBar.getHeight(), true);
//        frame.setExtendedState(Frame.MAXIMIZED_BOTH);
//    }
//
//    /**
//     * Sets anti-aliasing on or off. Anti-aliasing makes graphics
//     * look much smoother, but is very resource heavy. It is good
//     * for saving images that look professional. The default is off.
//     * 
//     * @param enabled The state of anti-aliasing.
//     */
//    public static void setAntiAliasing (boolean enabled) {
//        view.setSceneAntialiasingEnable(enabled);
//        antiAliasingButton.setSelected(enabled);
//        //System.out.println("Anti aliasing enabled: " + enabled);
//    }
//
//    /**
//     * Returns true if anti-aliasing is enabled.
//     */
//    public static boolean getAntiAliasing () { return antiAliasingButton.isSelected(); }
//
//    /**
//     * Sets the number of triangular divisons of curved objects. 
//     * The default is 100 divisons. Decrease this to increase performance.
//     * 
//     * @param N The number of divisions.
//     */
//    public static void setNumDivisions (int N) { numDivisions = N; }
//
//    /**
//     * Gets the number of triangular divisons of curved objects.
//     * 
//     * @return The number of divisions.
//     */
//    public static int  getNumDivisions () { return numDivisions; }
//
//    /**
//     * Gets the current camera mode.
//     * 
//     * @return The current camera mode.
//     */
//    public static int  getCameraMode () { return cameraMode; }
//
//    /**
//     * Sets the current camera mode. <br>
//     * -------------------------------------- <br>
//     * StdDraw3D.ORBIT_MODE: <br>
//     * Rotates and zooms around a central point. <br>
//     * <br>
//     * Mouse Click Left        - Orbit <br>
//     * Mouse Click Right       - Pan <br>
//     * Mouse Wheel / Alt-Click - Zoom <br>
//     * -------------------------------------- <br>
//     * StdDraw3D.FPS_MODE: <br>
//     * First-person-shooter style controls. <br>
//     * Up is always the positive y-axis. <br>
//     * <br>
//     * W/Up        - Forward <br>
//     * S/Down      - Backward <br>
//     * A/Left      - Left <br>
//     * D/Right     - Right <br>
//     * Q/Page Up   - Up <br>
//     * E/Page Down - Down <br>
//     * Mouse       - Look <br>
//     * -------------------------------------- <br>
//     * StdDraw3D.AIRPLANE_MODE: <br>
//     * Similar to fps_mode, but can be oriented <br>
//     * with up in any direction. <br>
//     * <br>
//     * W/Up        - Forward <br>
//     * S/Down      - Backward <br>
//     * A/Left      - Left <br>
//     * D/Right     - Right <br>
//     * Q/Page Up   - Rotate CCW <br>
//     * E/Page Down - Rotate CW <br>
//     * Mouse       - Look <br>
//     * -------------------------------------- <br>
//     * StdDraw3D.LOOK_MODE: <br>
//     * No movement, but uses mouse to look around. <br>
//     *  <br>
//     * Mouse - Look <br>
//     * -------------------------------------- <br>
//     * StdDraw3D.FIXED_MODE: <br>
//     * No movement or looking. <br>
//     * -------------------------------------- <br>
//     * @param mode The camera mode.
//     */
//    public static void setCameraMode (int mode) {
//
//        cameraMode = mode;
//
//        if (cameraMode == ORBIT_MODE) {
//            orbit.setRotateEnable(true);
//            if (view.getProjectionPolicy() != View.PARALLEL_PROJECTION)
//                orbit.setZoomEnable(true);
//            orbit.setTranslateEnable(true);
//            orbit.setRotationCenter(orbitCenter);
//            orbitModeButton.setSelected(true);
//        } else {
//            orbit.setRotateEnable(false);
//            orbit.setZoomEnable(false);
//            orbit.setTranslateEnable(false);
//        }
//        if (cameraMode == FPS_MODE) {
//            fpsModeButton.setSelected(true);
//            camera.rotateFPS(0, 0, 0);
//        }
//        if (cameraMode == AIRPLANE_MODE) {
//            airplaneModeButton.setSelected(true);
//        }
//        if (cameraMode == LOOK_MODE) {
//            //System.out.println("Camera in look mode.");
//            lookModeButton.setSelected(true);
//        }
//        if (cameraMode == FIXED_MODE) {
//            //System.out.println("Camera in fixed mode.");
//            fixedModeButton.setSelected(true);
//        }
//        if (cameraMode == IMMERSIVE_MODE) {
//
//            BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
//            Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
//                    cursorImg, new java.awt.Point(0, 0), "blank cursor");
//            frame.getContentPane().setCursor(blankCursor);
//            //System.out.println("Camera in fps mode.");
//        } else {
//            frame.getContentPane().setCursor(Cursor.getDefaultCursor());
//        }
//    }
//
//    /**
//     * Sets the default camera mode.
//     */
//    public static void setCameraMode() { setCameraMode(DEFAULT_CAMERA_MODE); }
//
//    /**
//     * Sets the center of rotation for the camera mode ORBIT_MODE.
//     */
//    public static void setOrbitCenter(double x, double y, double z) {
//        setOrbitCenter(new Point3d(x, y, z));
//    }
//
//    /**
//     * Sets the center of rotation for the camera mode ORBIT_MODE.
//     */
//    public static void setOrbitCenter(Vector3D v) {
//        setOrbitCenter(new Point3d(v.x, v.y, v.z)); 
//    }
//
//    private static void setOrbitCenter(Point3d center) {
//        orbitCenter = center;
//        orbit.setRotationCenter(orbitCenter);
//    }
//
//    public static Vector3D getOrbitCenter() {
//        return new Vector3D(orbitCenter);
//    }
//
//    /**
//     * Sets the projection mode to perspective projection with a
//     * default field of view. In this mode, closer objects appear
//     * larger, as in real life. 
//     */
//    public static void setPerspectiveProjection () {
//        setPerspectiveProjection(DEFAULT_FOV);
//    }
//
//    /**
//     * Sets the projection mode to perspective projection with the
//     * given field of view. In this mode, closer objects appear
//     * larger, as in real life. A larger field of view means that
//     * there is more perspective distortion. Reasonable values are
//     * between 0.5 and 3.0.
//     * 
//     * @param fov The field of view to use. Try [0.5-3.0].
//     */
//    public static void setPerspectiveProjection (double fov) {
//
//        view.setProjectionPolicy(View.PERSPECTIVE_PROJECTION);
//        view.setWindowEyepointPolicy(View.RELATIVE_TO_FIELD_OF_VIEW);
//        view.setFieldOfView(fov);
//        setScreenScale(1);
//        orbit.setZoomEnable(true);
//        perspectiveButton.setSelected(true);
//        if ((Double)fovSpinner.getValue() != fov)
//            fovSpinner.setValue(fov);
//        //if (view.getProjectionPolicy() == View.PERSPECTIVE_PROJECTION) return;
//    }
//
//    /** 
//     * Sets the projection mode to orthographic projection.
//     * In this mode, parallel lines remain parallel after
//     * projection, and there is no perspective. It is as 
//     * looking from infinitely far away with a telescope.
//     * AutoCAD programs use this projection mode.
//     */
//    public static void setParallelProjection () {
//
//        if (view.getProjectionPolicy() == View.PARALLEL_PROJECTION) return;
//        view.setProjectionPolicy(View.PARALLEL_PROJECTION);
//        orbit.setZoomEnable(false);
//        parallelButton.setSelected(true);
//
//        setScreenScale(0.3 / zoom);
//    }
//
//    private static void setScreenScale (double scale) {
//        double ratio = scale / view.getScreenScale();
//        view.setScreenScale(scale);
//        view.setFrontClipDistance(view.getFrontClipDistance() * ratio);
//        view.setBackClipDistance(view.getBackClipDistance() * ratio);
//    }
//
//    /* ***************************************************************
//     *              Mouse Listener Methods                           *
//     *****************************************************************/
//
//    /** Is any mouse button pressed? */
//    public static boolean mousePressed () {
//        synchronized (mouseLock) {
//            return (mouse1Pressed() || mouse2Pressed() || mouse3Pressed());
//        }
//    }
//
//    /**
//     * Is the mouse1 button pressed down?
//     */
//    public static boolean mouse1Pressed () { 
//        synchronized (mouseLock) {
//            return mouse1; 
//        }
//    }
//
//    /**
//     * Is the mouse2 button pressed down?
//     */
//    public static boolean mouse2Pressed () { 
//        synchronized (mouseLock) {
//            return mouse2; 
//        }
//    }
//
//    /**
//     * Is the mouse3 button pressed down?
//     */
//    public static boolean mouse3Pressed () { 
//        synchronized (mouseLock) {
//            return mouse3; 
//        }
//    }
//
//    /**
//     * Where is the mouse?
//     * 
//     * @return The value of the X-coordinate of the mouse.
//     */
//    public static double mouseX () {
//        synchronized (mouseLock) {
//            return unscaleX(mouseX);       
//        }
//    }
//
//    /**
//     * Where is the mouse?
//     * @return The value of the Y-coordinate of the mouse.
//     */
//    public static double mouseY () { 
//        synchronized (mouseLock) {
//            return unscaleY(mouseY);       
//        }
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseClicked (MouseEvent e) {
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseEntered (MouseEvent e) { }
//
//    // FIX THIS
//    private static boolean robotMove = false;
//    private static int oldX;
//    private static int oldY;
//    private static boolean fakeEvent = false;
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseExited  (MouseEvent e) { 
//
//    }
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mousePressed (MouseEvent e) {
//        synchronized (mouseLock) {
//            mouseX = e.getX();
//            mouseY = e.getY();
//            if (e.getButton() == 1) mouse1 = true;
//            if (e.getButton() == 2) mouse2 = true;
//            if (e.getButton() == 3) mouse3 = true;
//            //System.out.println("Mouse button = " + e.getButton());
//        }
//    }
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseReleased (MouseEvent e) { 
//        synchronized (mouseLock) {
//            if (e.getButton() == 1) mouse1 = false;
//            if (e.getButton() == 2) mouse2 = false;
//            if (e.getButton() == 3) mouse3 = false;
//        }
//    }
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseDragged (MouseEvent e)  {
//        synchronized (mouseLock) {
//
//            mouseMotionEvents(e, e.getX(), e.getY(), true);
//            mouseX = e.getX();
//            mouseY = e.getY();
//        }
//    }
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseMoved (MouseEvent e) {
//        synchronized (mouseLock) {
//
//            //System.out.println(e.getX() + " " + e.getY());
//            mouseMotionEvents(e, e.getX(), e.getY(), false);
//
//            if (!fakeEvent) {
//                mouseX = e.getX();
//                mouseY = e.getY();
//            } else fakeEvent = false;
//
//            if (robotMove) {
//                robotMove = false;
//                MouseEvent fake = new MouseEvent(frame, MouseEvent.MOUSE_MOVED, 0, 0, oldX, oldY, 0, false);
//                System.out.println("Calling fake event");
//                fakeEvent = true;
//                //mouseMoved(fake);
//            }
//        }
//    }    
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void mouseWheelMoved (MouseWheelEvent e) {
//
//        double notches = e.getWheelRotation();
//        //System.out.println(notches);
//        if ((cameraMode == ORBIT_MODE) && (view.getProjectionPolicy() == View.PARALLEL_PROJECTION)) {
//            camera.moveRelative(0, 0, notches * zoom / 20);
//        }
//    }
//
//    private static void mouseMotionEvents (MouseEvent e, double newX, double newY, boolean dragged) {
//
//        //System.out.println("x = " + mouseX() + " y = " + mouseY());
//
//        if (cameraMode == FIXED_MODE) return;
//
//        if (cameraMode == FPS_MODE) {
//            if (dragged || immersive) {
//                camera.rotateFPS((mouseY - newY)/4, (mouseX - newX)/4, 0);
//            }
//            return;
//        }
//
//        if ((cameraMode == AIRPLANE_MODE)) {
//            if (dragged || immersive)
//                camera.rotateRelative((mouseY - newY)/4, (mouseX - newX)/4, 0);
//            return;
//        }
//
//        if ((cameraMode == LOOK_MODE)) {
//            if (dragged || immersive)
//                camera.rotateFPS((mouseY - newY)/4, (mouseX - newX)/4, 0);
//            return;
//        }
//
//        if ((cameraMode == ORBIT_MODE) && (dragged && isKeyPressed(KeyEvent.VK_ALT)) && (view.getProjectionPolicy() == View.PARALLEL_PROJECTION)) {
//            camera.moveRelative(0, 0, (newY - mouseY) * zoom / 50);
//            return;
//        }
//
//        //         if (immersive) {
//        //             rotateEyeFPS((mouseY - newY)/200, (mouseX - newX)/200, 0);
//        // 
//        //             double dx = newX - mouseX;
//        //             double dy = newY - mouseY;
//        //             double d = dx * dx + dy * dy;
//        //             //System.out.println(d);
//        //             if (d > 50000) return;
//        // 
//        //             double xs = e.getXOnScreen();
//        //             double ys = e.getYOnScreen();
//        //             if ((xs == 299) && (ys == 299)) {
//        //                 System.out.println("ROBOT MOVE");
//        //                 return;
//        //             }
//        //         }
//    }
//
//    /* ***************************************************************
//     *              Keyboard Listener Methods                        *
//     *****************************************************************/
//
//    /**
//     * Has the user typed a key?
//     * 
//     * @return True if the user has typed a key, false otherwise.
//     */
//    public static boolean hasNextKeyTyped () { 
//        synchronized (keyLock) {
//            return !keysTyped.isEmpty();
//        }
//    }
//
//    /**
//     * What is the next key that was typed by the user?
//     * 
//     * @return The next key typed.
//     */
//    public static char nextKeyTyped () {
//        synchronized (keyLock) {
//            return keysTyped.removeLast();
//        }
//    }
//
//    /**
//     * Is the given key currently pressed down? The keys correnspond
//     * to physical keys, not characters. For letters, use the 
//     * uppercase character to refer to the key. For arrow keys and
//     * modifiers such as shift and ctrl, refer to the KeyEvent 
//     * constants, such as KeyEvent.VK_SHIFT.
//     * 
//     * @param key The given key
//     * @return True if the given character is pressed.
//     */
//    public static boolean isKeyPressed (int key) {
//        synchronized (keyLock) {
//            return keysDown.contains(key);
//        }
//    }
//
//    /**
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void keyTyped (KeyEvent e) {
//        synchronized (keyLock) {
//
//            char c = e.getKeyChar();
//            keysTyped.addFirst(c);
//
//            if (c == '`') setCameraMode((getCameraMode() + 1) % 5);
//        }
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void keyPressed (KeyEvent e)   {
//        synchronized (keyLock) {
//            keysDown.add(e.getKeyCode());
//            //System.out.println((int)e.getKeyCode() + " pressed");
//        }
//    }
//
//    /** 
//     * This method cannot be called directly.
//     * @deprecated
//     */
//    @Deprecated
//	@Override
//	public void keyReleased (KeyEvent e)   {
//        synchronized (keyLock) {
//            keysDown.remove(e.getKeyCode());
//            //System.out.println((int)e.getKeyCode() + " released");
//        }
//    }
//
//    /**
//     * Processes one frame of keyboard events.
//     * 
//     * @param time The amount of time for this frame.
//     */
//    private static void moveEvents (int time) {
//
//        infoDisplay();
//
//        if (isKeyPressed(KeyEvent.VK_CONTROL)) return;
//
//        if (cameraMode == FPS_MODE) {
//            double move = 0.00015 * time * (zoom);
//            if (isKeyPressed('W') || isKeyPressed(KeyEvent.VK_UP))        camera.moveRelative(0,     0,  move * 3);
//            if (isKeyPressed('S') || isKeyPressed(KeyEvent.VK_DOWN))      camera.moveRelative(0,     0, -move * 3);
//            if (isKeyPressed('A') || isKeyPressed(KeyEvent.VK_LEFT))      camera.moveRelative(-move, 0, 0);
//            if (isKeyPressed('D') || isKeyPressed(KeyEvent.VK_RIGHT))     camera.moveRelative( move, 0, 0);
//            if (isKeyPressed('Q') || isKeyPressed(KeyEvent.VK_PAGE_UP))   camera.moveRelative(0,     move, 0);
//            if (isKeyPressed('E') || isKeyPressed(KeyEvent.VK_PAGE_DOWN)) camera.moveRelative(0,    -move, 0);
//        }
//        if (cameraMode == AIRPLANE_MODE) {
//            double move = 0.00015 * time * (zoom);
//            if (isKeyPressed('W') || isKeyPressed(KeyEvent.VK_UP))        camera.moveRelative(0,     0,  move * 3);
//            if (isKeyPressed('S') || isKeyPressed(KeyEvent.VK_DOWN))      camera.moveRelative(0,     0, -move * 3);
//            if (isKeyPressed('A') || isKeyPressed(KeyEvent.VK_LEFT))      camera.moveRelative(-move, 0, 0);
//            if (isKeyPressed('D') || isKeyPressed(KeyEvent.VK_RIGHT))     camera.moveRelative( move, 0, 0);
//            if (isKeyPressed('Q') || isKeyPressed(KeyEvent.VK_PAGE_UP))   camera.rotateRelative(0, 0,  move * 250 / zoom);
//            if (isKeyPressed('E') || isKeyPressed(KeyEvent.VK_PAGE_DOWN)) camera.rotateRelative(0, 0, -move * 250 / zoom);
//        }
//    }
//
//    /* ***************************************************************
//     *             Action Listener Methods                           *
//     *****************************************************************/
//
//    private static void save3DAction () {
//
//        FileDialog chooser = new FileDialog(StdDraw3D.frame, "Save as a 3D file for loading later.", FileDialog.SAVE);
//        chooser.setVisible(true);
//        String filename = chooser.getFile();
//        if (filename != null) {
//            StdDraw3D.saveScene3D(chooser.getDirectory() + File.separator + chooser.getFile());
//        }
//
//        keysDown.remove(KeyEvent.VK_META);
//        keysDown.remove(KeyEvent.VK_CONTROL);
//        keysDown.remove(KeyEvent.VK_E);
//    }   
//
//    private static void loadAction () {
//
//        FileDialog chooser = new FileDialog(frame, "Pick a .obj or .ply file to load.", FileDialog.LOAD);
//        chooser.setVisible(true);
//        String filename = chooser.getDirectory() + chooser.getFile();
//        model(filename);
//
//        keysDown.remove(KeyEvent.VK_META);
//        keysDown.remove(KeyEvent.VK_CONTROL);
//        keysDown.remove(KeyEvent.VK_L);
//    }
//
//    private static void saveAction () {
//
//        FileDialog chooser = new FileDialog(frame, "Use a .png or .jpg extension.", FileDialog.SAVE);
//        chooser.setVisible(true);
//        String filename = chooser.getFile();
//
//        if (filename != null) {
//            StdDraw3D.save(chooser.getDirectory() + File.separator + chooser.getFile());
//        }
//
//        keysDown.remove(KeyEvent.VK_META);
//        keysDown.remove(KeyEvent.VK_CONTROL);
//        keysDown.remove(KeyEvent.VK_S);
//    }   
//
//    private static void quitAction () {
//
//        WindowEvent wev = new WindowEvent(frame, WindowEvent.WINDOW_CLOSING);
//        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
//
//        keysDown.remove(KeyEvent.VK_META);
//        keysDown.remove(KeyEvent.VK_CONTROL);
//        keysDown.remove(KeyEvent.VK_Q);
//    }
//
//    /* ***************************************************************
//     *             Light and Background Methods                      *
//     *****************************************************************/
//
//    /**
//     * Sets the background color to the given color.
//     * 
//     * @param color The color to set the background as.
//     */
//    public static void setBackground (Color color) {
//
//        rootGroup.removeChild(bgGroup);
//        bgGroup.removeChild(background);
//
//        background = createBackground();
//        background.setColor(new Color3f(color));
//
//        bgGroup.addChild(background);
//        rootGroup.addChild(bgGroup);
//    }
//
//    /**
//     * Sets the background image to the given filename, scaled to fit the window.
//     * 
//     * @param imageURL The filename for the background image.
//     */
//    public static void setBackground (String imageURL) {
//
//        rootGroup.removeChild(bgGroup);
//        bgGroup.removeChild(background);
//
//        background = createBackground();
//
//        BufferedImage bi = null;
//        try { bi = ImageIO.read(new File(imageURL)); }
//        catch (IOException ioe) {ioe.printStackTrace(); }
//
//        if (bi == null) {
//            try { ImageIO.read(new URL(imageURL)); }
//            catch (Exception e) { e.printStackTrace(); }
//        }
//
//        ImageComponent2D imageComp = new ImageComponent2D(ImageComponent.FORMAT_RGB, bi);
//        background.setImage(imageComp);
//        background.setImageScaleMode(Background.SCALE_FIT_ALL);
//
//        bgGroup.addChild(background);
//        rootGroup.addChild(bgGroup);
//    }
//
//    /**
//     * Sets the background to the given image file. The file gets
//     * wrapped around as a spherical skybox.
//     * 
//     * @param imageURL The background image to use.
//     */
//    public static void setBackgroundSphere (String imageURL) {
//
//        Sphere sphere = new Sphere(1.1f, Sphere.GENERATE_NORMALS
//                | Sphere.GENERATE_NORMALS_INWARD
//                | Sphere.GENERATE_TEXTURE_COORDS, numDivisions);
//
//        Appearance ap = sphere.getAppearance();
//
//        Texture texture = createTexture(imageURL);
//
//        TextureAttributes texAttr = new TextureAttributes();
//        texAttr.setTextureMode(TextureAttributes.REPLACE);
//
//        ap.setTexture(texture);
//        ap.setTextureAttributes(texAttr);
//
//        sphere.setAppearance(ap);
//
//        BranchGroup backGeoBranch = createBranchGroup();
//        backGeoBranch.addChild(sphere);
//
//        rootGroup.removeChild(bgGroup);
//        bgGroup.removeChild(background);
//
//        background = createBackground();
//        background.setGeometry(backGeoBranch);
//
//        bgGroup.addChild(background);
//        rootGroup.addChild(bgGroup);
//
//    }
//
//    //*********************************************************************************************
//
//    public static void clearSound () {
//        soundGroup.removeAllChildren();
//    }
//
//    public static void playAmbientSound (String filename) { 
//        playAmbientSound(filename, 1.0, false); 
//    }
//
//    public static void playAmbientSound (String filename, boolean loop) {
//        playAmbientSound(filename, 1.0, loop);
//    }
//
//    private static void playAmbientSound (String filename, double volume, boolean loop) {
//
//        MediaContainer mc = new MediaContainer("file:" + filename);
//        mc.setCacheEnable(true);
//
//        BackgroundSound sound = new BackgroundSound();
//        sound.setInitialGain((float)volume);
//        sound.setSoundData(mc);
//        sound.setBounds(INFINITE_BOUNDS);
//        sound.setSchedulingBounds(INFINITE_BOUNDS);
//        if (loop == true) sound.setLoop(Sound.INFINITE_LOOPS);
//        sound.setEnable(true);
//
//        BranchGroup bg = createBranchGroup();
//        bg.addChild(sound);
//        soundGroup.addChild(bg);
//    }
//
//    //     public static void playPointSound (String filename, Vector3D position, double volume, boolean loop) {
//    // 
//    //         MediaContainer mc = new MediaContainer("file:" + filename);
//    //         mc.setCacheEnable(true);
//    // 
//    //         PointSound sound = new PointSound();
//    //         sound.setInitialGain((float)volume);
//    //         sound.setSoundData(mc);
//    //         sound.setBounds(INFINITE_BOUNDS);
//    //         sound.setSchedulingBounds(INFINITE_BOUNDS);
//    //         if (loop == true) sound.setLoop(Sound.INFINITE_LOOPS);
//    //         sound.setPosition(createPoint3f(position));
//    //         Point2f[] distanceGain = new Point2f[2];
//    //         distanceGain[0] = new Point2f(0, 1);
//    //         distanceGain[1] = new Point2f(20, 0);
//    //         sound.setDistanceGain(distanceGain);
//    //         sound.setEnable(true);
//    // 
//    //         BranchGroup bg = createBranchGroup();
//    //         bg.addChild(sound);
//    //         soundGroup.addChild(bg);
//    //     }
//    //*********************************************************************************************
//
//    public static void clearFog () {
//        fogGroup.removeAllChildren();
//    }
//
//    public static void addFog (Color col, double frontDistance, double backDistance) {
//        LinearFog fog = new LinearFog(new Color3f(col), frontDistance, backDistance);
//        fog.setInfluencingBounds(INFINITE_BOUNDS);
//
//        BranchGroup bg = createBranchGroup();
//        bg.addChild(fog);
//        fogGroup.addChild(bg);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Removes all current lighting from the scene.
//     */
//    public static void clearLight () {
//        lightGroup.removeAllChildren();    
//    }
//
//    /**
//     * Adds the default lighting to the scene, called automatically at startup.
//     */
//    public static void setDefaultLight () {
//        clearLight();
//        directionalLight(-4f, 7f, 12f, LIGHT_GRAY);
//        directionalLight(4f, -7f, -12f, WHITE);
//        ambientLight(new Color(0.1f, 0.1f, 0.1f));
//    }
//
//    /**
//     * Adds a directional light of color col which appears to come from (x, y, z).
//     */
//    public static Light directionalLight (Vector3D dir, Color col) {
//        return directionalLight(dir.x, dir.y, dir.z, col);
//    }
//
//    /**
//     * Adds a directional light of color col which shines in the direction vector (x, y, z)
//     */
//    public static Light directionalLight (double x, double y, double z, Color col) {
//
//        DirectionalLight light = new DirectionalLight();
//        light.setColor(new Color3f(col));
//
//        light.setInfluencingBounds(INFINITE_BOUNDS);
//        light.setCapability(DirectionalLight.ALLOW_STATE_WRITE);
//        light.setCapability(DirectionalLight.ALLOW_COLOR_WRITE);
//        light.setEnable(true);
//
//        BranchGroup bg = createBranchGroup();
//        TransformGroup tg = createTransformGroup();
//        tg.addChild(light);
//        bg.addChild(tg);
//        lightGroup.addChild(bg);
//
//        Light l = new Light(bg, tg, light);
//        l.setDirection(new Vector3D(x, y, z));
//        return l;
//    }
//
//    /**
//     * Adds ambient light of color col.
//     */
//    public static Light ambientLight (Color col) {
//
//        Color3f lightColor = new Color3f(col);
//        AmbientLight light = new AmbientLight(lightColor);
//
//        light.setInfluencingBounds(INFINITE_BOUNDS);
//        light.setCapability(AmbientLight.ALLOW_STATE_WRITE);
//        light.setCapability(AmbientLight.ALLOW_COLOR_WRITE);
//
//        BranchGroup bg = createBranchGroup();
//        TransformGroup tg = createTransformGroup();
//        tg.addChild(light);
//        bg.addChild(tg);
//        lightGroup.addChild(bg);
//
//        return new Light(bg, tg, light);
//    }
//
//    public static Light pointLight (Vector3D origin, Color col) {
//        return pointLight(origin.x, origin.y, origin.z, col, 1.0);
//    }
//    
//    public static Light pointLight (double x, double y, double z, Color col) {
//        return pointLight(x, y, z, col, 1.0);
//    }
//
//    public static Light pointLight (Vector3D origin, Color col, double power) {
//        return pointLight(origin.x, origin.y, origin.z, col, power);
//    }
//    
//    public static Light pointLight (double x, double y, double z, Color col, double power) {
//
//        PointLight light = new PointLight();
//        light.setColor(new Color3f(col));
//
//        light.setInfluencingBounds(INFINITE_BOUNDS);
//        light.setCapability(PointLight.ALLOW_STATE_WRITE);
//        light.setCapability(PointLight.ALLOW_COLOR_WRITE);
//        light.setCapability(PointLight.ALLOW_ATTENUATION_WRITE);
//
//        float scale = (float)zoom;
//        float linearFade = 0.03f;
//        float quadraticFade = 0.03f;
//        light.setAttenuation(1.0f, linearFade / scale, quadraticFade / (scale * scale));
//
//        BranchGroup bg = createBranchGroup();
//        TransformGroup tg = createTransformGroup();
//        tg.addChild(light);
//        bg.addChild(tg);
//        lightGroup.addChild(bg);
//
//        Light l = new Light(bg, tg, light);
//        l.setPosition(x, y, z);
//        l.scalePower(power);
//        
//        return l;
//    }
//
//    /**
//     * Returns a randomly generated color.
//     */
//    public static Color randomColor () {
//        return new Color(new Random().nextInt());
//    }
//
//    /**
//     * Returns a randomly generated color on the rainbow.
//     */
//    public static Color randomRainbowColor () {
//        return Color.getHSBColor((float)Math.random(), 1.0f, 1.0f);
//    }
//
//    /**
//     * Returns a randomly generated normalized Vector3D.
//     */
//    public static Vector3D randomDirection() {
//
//        double theta = Math.random() * Math.PI * 2;
//        double phi   = Math.random() * Math.PI;
//        return new Vector3D(Math.cos(theta) * Math.sin(phi), Math.sin(theta) * Math.sin(phi), Math.cos(phi));
//    }
//
//    /* ***************************************************************
//     *              Animation Frame Methods                          *
//     *****************************************************************/
//
//    /**
//     * Clears the entire on the next call of show().
//     */
//    public static void clear () {
//        clear3D();
//        clearOverlay();
//    }
//
//    /**
//     * Clears the 3D world on the screen for the next call of show();
//     */
//    public static void clear3D() {
//        clear3D = true;
//        offscreenGroup = createBranchGroup();
//    }
//
//    /**
//     * Clears the 2D overlay for the next call of show();
//     */
//    public static void clearOverlay () {
//        clearOverlay = true;
//        offscreenImage = createBufferedImage();
//    }
//
//    /**
//     * Pauses for a given amount of milliseconds.
//     * 
//     * @param time The number of milliseconds to pause for.
//     */
//    public static void pause (int time) {
//
//        int t = time;
//
//        while (t > 10) {
//            moveEvents(10);
//            Toolkit.getDefaultToolkit().sync();
//            try { Thread.currentThread();
//			Thread.sleep(10); }
//            catch (InterruptedException e) { System.out.println("Error sleeping"); }
//
//            t -= 10;
//        }
//
//        moveEvents(t);
//        if (t == 0) return;
//        try { Thread.currentThread();
//		Thread.sleep(t); }
//        catch (InterruptedException e) { System.out.println("Error sleeping"); }
//    }
//
//    /**
//     * Allows for camera navigation of a scene without redrawing. Useful when
//     * drawing a complicated scene once and then exploring without redrawing.
//     * Call only as the last line of a program.
//     */
//    public static void finished () { 
//
//        show(1000000000);
//    }
//
//    /**
//     * Renders to the screen, but does not pause.
//     */
//    public static void show () { show(0); }
//
//    /**
//     * Renders drawn 3D objects to the screen and draws the 
//     * 2D overlay, then pauses for the given time.
//     * 
//     * @param time The number of milliseconds to pause for.
//     */
//    public static void show (int time) {
//
//        renderOverlay();
//        render3D();
//        pause(time);
//    }
//
//    public static void showOverlay () { showOverlay(0); }
//
//    /**
//     * Displays the drawn overlay onto the screen.
//     */
//    public static void showOverlay (int time) {
//
//        renderOverlay();
//        pause(time);
//    }
//
//    private static void renderOverlay () {
//        if (clearOverlay) {
//            clearOverlay = false;
//            onscreenImage = offscreenImage;
//        } else {
//            Graphics2D graphics = (Graphics2D) onscreenImage.getGraphics();
//            graphics.drawRenderedImage(offscreenImage, new AffineTransform());
//        }
//        offscreenImage = createBufferedImage();
//    }
//
//    public static void show3D () { show3D(0); }
//
//    /**
//     * Renders the drawn 3D objects to the screen, but not the overlay.
//     */
//    public static void show3D (int time) {
//
//        render3D();
//        pause(time);
//    }
//
//    private static void render3D () {
//        rootGroup.addChild(offscreenGroup);
//
//        if (clear3D) {
//            clear3D = false;
//            rootGroup.removeChild(onscreenGroup);
//            onscreenGroup = offscreenGroup;
//        } else {
//            Enumeration children = offscreenGroup.getAllChildren();
//            while(children.hasMoreElements()) {
//                Node child = (Node)children.nextElement();
//                offscreenGroup.removeChild(child);
//                onscreenGroup.addChild(child);
//            }
//        }
//
//        offscreenGroup = createBranchGroup();
//
//        //System.out.println("off = " + offscreenGroup.numChildren());
//        //System.out.println("on  = " + onscreenGroup.numChildren());
//
//        rootGroup.removeChild(offscreenGroup);
//    }
//    /* ***************************************************************
//     *                 Primitive 3D Drawing Methods                  *
//     *****************************************************************/
//
//    /**
//     * Draws a sphere at (x, y, z) with radius r.
//     */
//    public static Shape sphere (double x, double y, double z, double r) {
//        return sphere(x, y, z, r, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws a sphere at (x, y, z) with radius r and axial rotations (xA, yA, zA).
//     */
//    public static Shape sphere (double x, double y, double z, double r, double xA, double yA, double zA) {
//        return sphere(x, y, z, r, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws a sphere at (x, y, z) with radius r and a texture from imageURL.
//     */
//    public static Shape sphere (double x, double y, double z, double r, String imageURL) {
//        return sphere(x, y, z, r, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws a sphere at (x, y, z) with radius r, axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape sphere (double x, double y, double z, double r, double xA, double yA, double zA, String imageURL) {
//
//        Vector3f dimensions = createVector3f(0, 0, r);
//        Sphere sphere = new Sphere(dimensions.z, PRIMFLAGS, numDivisions);
//        sphere.setAppearance(createAppearance(imageURL, true));
//        return primitive(sphere, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    /**
//     * Draws a wireframe sphere at (x, y, z) with radius r.
//     */
//    public static Shape wireSphere (double x, double y, double z, double r) {
//        return wireSphere(x, y, z, r, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe sphere at (x, y, z) with radius r and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireSphere (double x, double y, double z, double r, double xA, double yA, double zA) {
//
//        Vector3f dimensions = createVector3f(0, 0, r);
//        Sphere sphere = new Sphere(dimensions.z, PRIMFLAGS, numDivisions);
//        sphere.setAppearance(createAppearance(null, false));
//        return primitive(sphere, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws an ellipsoid at (x, y, z) with dimensions (w, h, d).
//     */
//    public static Shape ellipsoid (double x, double y, double z, double w, double h, double d) {
//        return ellipsoid(x, y, z, w, h, d, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws an ellipsoid at (x, y, z) with dimensions (w, h, d) and axial rotations (xA, yA, zA).
//     */
//    public static Shape ellipsoid (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA) {
//        return ellipsoid(x, y, z, w, h, d, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws an ellipsoid at (x, y, z) with dimensions (w, h, d) and a texture from imageURL.
//     */
//    public static Shape ellipsoid (double x, double y, double z, double w, double h, double d, String imageURL) {
//        return ellipsoid(x, y, z, w, h, d, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws an ellipsoid at (x, y, z) with dimensions (w, h, d), axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape ellipsoid (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA, String imageURL) {
//
//        Sphere sphere = new Sphere(1, PRIMFLAGS, numDivisions);
//        sphere.setAppearance(createAppearance(imageURL, true));
//        return primitive(sphere, x, y, z, new Vector3d(xA, yA, zA), new Vector3d(w, h, d));
//    }
//
//    /**
//     * Draws a wireframe ellipsoid at (x, y, z) with dimensions (w, h, d).
//     */
//    public static Shape wireEllipsoid (double x, double y, double z, double w, double h, double d) {
//        return wireEllipsoid(x, y, z, w, h, d, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe ellipsoid at (x, y, z) with dimensions (w, h, d) and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireEllipsoid (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA) {
//
//        Sphere sphere = new Sphere(1, PRIMFLAGS, numDivisions);
//        sphere.setAppearance(createAppearance(null, false));
//        return primitive(sphere, x, y, z, new Vector3d(xA, yA, zA), new Vector3d(w, h, d));
//    }
//
//    //*********************************************************************************************
//    /**
//     * Draws a cube at (x, y, z) with radius r.
//     */
//    public static Shape cube (double x, double y, double z, double r) {
//        return cube(x, y, z, r, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws a cube at (x, y, z) with radius r and axial rotations (xA, yA, zA).
//     */
//    public static Shape cube (double x, double y, double z, double r, double xA, double yA, double zA) {
//        return cube(x, y, z, r, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws a cube at (x, y, z) with radius r and a texture from imageURL.
//     */   
//    public static Shape cube (double x, double y, double z, double r, String imageURL) {
//        return cube(x, y, z, r, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws a cube at (x, y, z) with radius r, axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape cube (double x, double y, double z, double r, double xA, double yA, double zA, String imageURL) {
//        return box(x, y, z, r, r, r, xA, yA, zA, imageURL);
//    }
//
//    /**
//     * Draws a wireframe cube at (x, y, z) with radius r and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireCube (double x, double y, double z, double r, double xA, double yA, double zA) {
//
//        return wireBox(x, y, z, r, r, r, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe cube at (x, y, z) with radius r.
//     */
//    public static Shape wireCube (double x, double y, double z, double r) {
//
//        double[] xC = new double[] 
//            { x+r, x+r, x-r, x-r, x+r, x+r, x+r, x-r, x-r, x+r, x+r, x+r, x-r, x-r, x-r, x-r };
//        double[] yC = new double[] 
//            { y+r, y-r, y-r, y+r, y+r, y+r, y-r, y-r, y+r, y+r, y-r, y-r, y-r, y-r, y+r, y+r };
//        double[] zC = new double[] 
//            { z+r, z+r, z+r, z+r, z+r, z-r, z-r, z-r, z-r, z-r, z-r, z+r, z+r, z-r, z-r, z+r };
//
//        return lines(xC, yC, zC);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a box at (x, y, z) with dimensions (w, h, d).
//     */
//    public static Shape box (double x, double y, double z, double w, double h, double d) {
//        return box(x, y, z, w, h, d, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws a box at (x, y, z) with dimensions (w, h, d) and axial rotations (xA, yA, zA).
//     */
//    public static Shape box (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA) {
//        return box(x, y, z, w, h, d, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws a box at (x, y, z) with dimensions (w, h, d) and a texture from imageURL.
//     */
//    public static Shape box (double x, double y, double z, double w, double h, double d, String imageURL) {
//        return box(x, y, z, w, h, d, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws a box at (x, y, z) with dimensions (w, h, d), axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape box (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA, String imageURL) {
//
//        Appearance ap = createAppearance(imageURL, true);
//
//        Vector3f dimensions = createVector3f(w, h, d);
//
//        com.sun.j3d.utils.geometry.Box box = new 
//            com.sun.j3d.utils.geometry.Box(dimensions.x, dimensions.y, dimensions.z, PRIMFLAGS, ap, numDivisions);
//        return primitive(box, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    /**
//     * Draws a wireframe box at (x, y, z) with dimensions (w, h, d).
//     */
//    public static Shape wireBox (double x, double y, double z, double w, double h, double d) {
//        return wireBox(x, y, z, w, h, d, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe box at (x, y, z) with dimensions (w, h, d) and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireBox (double x, double y, double z, double w, double h, double d, double xA, double yA, double zA) {
//
//        Appearance ap = createAppearance(null, false);
//
//        Vector3f dimensions = createVector3f(w, h, d);
//
//        com.sun.j3d.utils.geometry.Box box = new 
//            com.sun.j3d.utils.geometry.Box(dimensions.x, dimensions.y, dimensions.z, PRIMFLAGS, ap, numDivisions);
//        return primitive(box, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a cylinder at (x, y, z) with radius r and height h.
//     */
//    public static Shape cylinder (double x, double y, double z, double r, double h) {
//        return cylinder(x, y, z, r, h, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws a cylinder at (x, y, z) with radius r, height h, and axial rotations (xA, yA, zA).
//     */
//    public static Shape cylinder (double x, double y, double z, double r, double h, double xA, double yA, double zA) {
//        return cylinder(x, y, z, r, h, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws a cylinder at (x, y, z) with radius r, height h, and a texture from imageURL.
//     */
//    public static Shape cylinder (double x, double y, double z, double r, double h, String imageURL) {
//        return cylinder(x, y, z, r, h, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws a cylinder at (x, y, z) with radius r, height h, axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape cylinder (double x, double y, double z, double r, double h, double xA, double yA, double zA, String imageURL) {
//
//        Appearance ap = createAppearance(imageURL, true);
//        Vector3f dimensions = createVector3f(r, h, 0);
//        Cylinder cyl = new Cylinder (dimensions.x, dimensions.y, PRIMFLAGS, numDivisions, numDivisions, ap);
//        return primitive(cyl, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    /**
//     * Draws a wireframe cylinder at (x, y, z) with radius r and height h.
//     */
//    public static Shape wireCylinder (double x, double y, double z, double r, double h) {
//        return wireCylinder(x, y, z, r, h, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe cylinder at (x, y, z) with radius r, height h, and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireCylinder (double x, double y, double z, double r, double h, double xA, double yA, double zA) {
//
//        Appearance ap = createAppearance(null, false);
//        Vector3f dimensions = createVector3f(r, h, 0);
//        Cylinder cyl = new Cylinder (dimensions.x, dimensions.y, PRIMFLAGS, numDivisions, numDivisions, ap);
//        return primitive(cyl, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a cone at (x, y, z) with radius r and height h.
//     */
//    public static Shape cone (double x, double y, double z, double r, double h) {
//        return cone(x, y, z, r, h, 0, 0, 0, null);
//    }
//
//    /**
//     * Draws a cone at (x, y, z) with radius r, height h, and axial rotations (xA, yA, zA).
//     */
//    public static Shape cone (double x, double y, double z, double r, double h, double xA, double yA, double zA) {
//        return cone(x, y, z, r, h, xA, yA, zA, null);
//    }
//
//    /**
//     * Draws a cone at (x, y, z) with radius r, height h, and a texture from imageURL.
//     */
//    public static Shape cone (double x, double y, double z, double r, double h, String imageURL) {
//        return cone(x, y, z, r, h, 0, 0, 0, imageURL);
//    }
//
//    /**
//     * Draws a cone at (x, y, z) with radius r, height h, axial rotations (xA, yA, zA), and a texture from imageURL.
//     */
//    public static Shape cone (double x, double y, double z, double r, double h, double xA, double yA, double zA, String imageURL) {
//
//        Appearance ap = createAppearance(imageURL, true);
//        Vector3f dimensions = createVector3f(r, h, 0);
//        Cone cone = new Cone (dimensions.x, dimensions.y, PRIMFLAGS, numDivisions, numDivisions, ap);
//        return primitive(cone, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    /**
//     * Draws a wireframe cone at (x, y, z) with radius r and height h.
//     */
//    public static Shape wireCone (double x, double y, double z, double r, double h) {
//        return wireCone(x, y, z, r, h, 0, 0, 0);
//    }
//
//    /**
//     * Draws a wireframe cone at (x, y, z) with radius r, height h, and axial rotations (xA, yA, zA).
//     */
//    public static Shape wireCone (double x, double y, double z, double r, double h, double xA, double yA, double zA) {
//
//        Appearance ap = createAppearance(null, false);
//        Vector3f dimensions = createVector3f(r, h, 0);
//        Cone cone = new Cone (dimensions.x, dimensions.y, PRIMFLAGS, numDivisions, numDivisions, ap);
//        return primitive(cone, x, y, z, new Vector3d(xA, yA, zA), null);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a Java 3D Primitive object at (x, y, z) with axial rotations (xA, yA, zA).
//     */
//    private static Shape primitive (Primitive shape, double x, double y, double z, Vector3d angles, Vector3d scales) {
//
//        shape.setCapability(Primitive.ENABLE_APPEARANCE_MODIFY);
//        shape.setPickable(false);
//        shape.setCollidable(false);
//
//        TransformGroup tgScale = createTransformGroup();
//        Transform3D scaleTransform = new Transform3D();
//        if (scales != null)
//            scaleTransform.setScale(scales);
//        tgScale.setTransform(scaleTransform);
//        tgScale.addChild(shape);
//
//        TransformGroup tgShape = createTransformGroup();
//        Transform3D transform = new Transform3D();
//        if (angles != null) {
//            angles.scale(Math.PI / 180);
//            transform.setEuler( angles);
//        }
//        Vector3f vector = createVector3f(x, y, z);
//        transform.setTranslation(vector);
//        tgShape.setTransform(transform);
//        tgShape.addChild(tgScale);
//
//        BranchGroup bg = createBranchGroup();
//        bg.addChild(tgShape);
//        offscreenGroup.addChild(bg);
//
//        return new Shape(bg, tgShape);
//    }
//
//    /* ***************************************************************
//     *             Non-Primitive Drawing Methods                     *
//     *****************************************************************/
//
//    /**
//     * Draws a single point at (x, y, z).
//     */
//    public static Shape point (double x, double y, double z) {
//
//        return points(new double[] {x}, new double[] {y}, new double[] {z});
//    }
//
//    /**
//     * Draws a set of points at the given coordinates. For example,
//     * the first point is at (x[0], y[0], z[0]).
//     * Much more efficient than drawing individual points.
//     */
//    public static Shape points (double[] x, double[] y, double[] z) {
//
//        Point3f[] coords = constructPoint3f(x, y, z);
//
//        GeometryArray geom = new PointArray(coords.length, PointArray.COORDINATES);
//        geom.setCoordinates(0, coords);
//
//        Shape3D shape = createShape3D(geom);
//
//        return shape(shape);
//    }
//
//    /**
//     * Draws a set of points at the given coordinates with the given colors. 
//     * For example, the first point is at (x[0], y[0], z[0]) with color colors[0].
//     * Much more efficient than drawing individual points.
//     */
//    public static Shape points (double[] x, double[] y, double[] z, Color[] colors) {
//
//        Point3f[] coords = constructPoint3f(x, y, z);
//
//        GeometryArray geom = new PointArray(coords.length, PointArray.COORDINATES | PointArray.COLOR_4);
//        geom.setCoordinates(0, coords);
//
//        for (int i = 0; i < x.length; i++)
//            geom.setColor(i, colors[i].getComponents(null));
//
//        Shape3D shape = createShape3D(geom);
//
//        return customShape(shape);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a single line from (x1, y1, z1) to (x2, y2, z2).
//     */
//    public static Shape line (double x1, double y1, double z1, double x2, double y2, double z2) {
//
//        return lines(new double[] {x1, x2}, new double[] {y1, y2}, new double[] {z1, z2});
//    }
//
//    /**
//     * Draws a set of connected lines. For example, the first line is from (x[0], y[0], z[0]) to
//     * (x[1], y[1], z[1]), and the second line is from (x[1], y[1], z[1]) to (x[2], y[2], z[2]).
//     * Much more efficient than drawing individual lines.
//     */
//    public static Shape lines (double[] x, double[] y, double[] z) {
//
//        Point3f[] coords = constructPoint3f(x, y, z);
//
//        GeometryArray geom = new LineStripArray
//            (coords.length, LineArray.COORDINATES, new int[] {coords.length});
//        geom.setCoordinates(0, coords);
//
//        Shape3D shape = createShape3D(geom);
//
//        return shape(shape);
//    }
//
//    /**
//     * Draws a set of connected lines. For example, the first line is from (x[0], y[0], z[0]) to
//     * (x[1], y[1], z[1]), and the second line is from (x[1], y[1], z[1]) to (x[2], y[2], z[2]).
//     * Much more efficient than drawing individual lines. Vertex colors are specified by the given
//     * array, and line colors are blends of its two vertex colors.
//     */
//    public static Shape lines (double[] x, double[] y, double[] z, Color[] colors) {
//
//        Point3f[] coords = constructPoint3f(x, y, z);
//
//        GeometryArray geom = new LineStripArray
//            (coords.length, LineArray.COORDINATES | LineArray.COLOR_4, new int[] {coords.length});
//        geom.setCoordinates(0, coords);
//
//        for (int i = 0; i < x.length; i++)
//            geom.setColor(i, colors[i].getComponents(null));
//
//        Shape3D shape = createShape3D(geom);
//
//        return customShape(shape);
//    }
//
//    /**
//     * Draws a cylindrical tube of radius r from vertex (x1, y1, z1) to vertex (x2, y2, z2).
//     */
//    public static Shape tube (double x1, double y1, double z1, double x2, double y2, double z2, double r) {
//
//        Vector3D mid = new Vector3D(x1 + x2, y1 + y2, z1 + z2).times(0.5);
//        Vector3D line = new Vector3D(x2 - x1, y2 - y1, z2 - z1);
//
//        Shape s = cylinder(mid.x, mid.y, mid.z, r, line.mag());
//
//        Vector3D yAxis = new Vector3D(0, 1, 0);
//        Vector3D cross = line.cross(yAxis);
//        double angle = line.angle(yAxis);
//        s.rotateAxis(cross, -angle);
//
//        return combine(s);
//    }
//
//    /**
//     * Draws a series of cylindrical tubes of radius r, with vertices (x, y, z).
//     */
//    public static Shape tubes (double[] x, double[] y, double[] z, double r) {
//
//        StdDraw3D.Shape[] shapes = new StdDraw3D.Shape[(x.length-1) * 2];
//
//        for (int i = 0; i < x.length - 1; i++) {
//            shapes[i]  = tube(x[i], y[i], z[i], x[i+1], y[i+1], z[i+1], r);
//            shapes[i + x.length - 1] = sphere(x[i+1], y[i+1], z[i+1], r); 
//        }
//
//        return combine(shapes);
//    }
//
//    /**
//     * Draws a series of cylindrical tubes of radius r, with vertices (x, y, z) and the given colors.
//     * No more efficient than drawing individual tubes.
//     */
//    public static Shape tubes (double[] x, double[] y, double[] z, double r, Color[] colors) {
//
//        StdDraw3D.Shape[] shapes = new StdDraw3D.Shape[(x.length-1) * 2];
//
//        for (int i = 0; i < x.length - 1; i++) {
//            StdDraw3D.setPenColor(colors[i]);
//            shapes[i]  = tube(x[i], y[i], z[i], x[i+1], y[i+1], z[i+1], r);
//            shapes[i + x.length - 1] = sphere(x[i+1], y[i+1], z[i+1], r); 
//        }
//
//        return combine(shapes);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a filled polygon with the given vertices. The vertices should be planar for a
//     * proper 2D polygon to be drawn.
//     */
//    public static Shape polygon (double[] x, double[] y, double[] z) {
//        return polygon(x, y, z, true);
//    }
//
//    /**
//     * Draws the triangulated outline of a polygon with the given vertices.
//     */
//    public static Shape wirePolygon (double[] x, double[] y, double[] z) {
//        return polygon(x, y, z, false);
//    }
//
//    /**
//     * Draws a polygon with the given vertices, which is filled or outlined based on the argument.
//     * 
//     * @param filled Is the polygon filled?
//     */
//    private static Shape polygon (double[] x, double[] y, double[] z, boolean filled) {
//
//        Point3f[] coords = constructPoint3f(x, y, z);
//        GeometryArray geom = new TriangleFanArray(coords.length, LineArray.COORDINATES, new int[] {coords.length});
//        geom.setCoordinates(0, coords);
//
//        GeometryInfo geoinfo = new GeometryInfo((GeometryArray)geom);
//        NormalGenerator normalGenerator = new NormalGenerator();
//        normalGenerator.generateNormals(geoinfo);
//
//        Shape3D shape = createShape3D(geoinfo.getIndexedGeometryArray());
//
//        if (filled)
//            return shape(shape);
//        else
//            return wireShape(shape);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws triangles which are defined by x1=points[i][0], y1=points[i][1], z1=points[i][2], x2=points[i][3], etc.
//     * All of the points will be the width and color specified by the setPenColor and setPenWidth methods.
//     * @param points an array of the points to be connected.  The first dimension is
//     * unspecified, but the second dimension should be 9 (the 3-space coordinates of each vertex)
//     */
//    public static Shape triangles (double[][] points) {
//        return triangles(points, true);
//    }
//
//    public static Shape wireTriangles (double[][] points) {
//        return triangles(points, false);
//    }
//
//    private static Shape triangles (double[][] points, boolean filled) {
//
//        int size = points.length;
//        Point3f[] coords = new Point3f[size*3];
//
//        for (int i = 0; i < size; i++) {
//            coords[3*i]   = new Point3f(createVector3f(points[i][0], points[i][1], points[i][2]));
//            coords[3*i+1] = new Point3f(createVector3f(points[i][3], points[i][4], points[i][5]));
//            coords[3*i+2] = new Point3f(createVector3f(points[i][6], points[i][7], points[i][8]));
//        }
//
//        GeometryArray geom = new TriangleArray(size*3, TriangleArray.COORDINATES);
//        geom.setCoordinates(0, coords);
//
//        GeometryInfo geoinfo = new GeometryInfo(geom);
//        NormalGenerator normalGenerator = new NormalGenerator();
//        normalGenerator.generateNormals(geoinfo);
//
//        Shape3D shape = createShape3D(geoinfo.getIndexedGeometryArray());
//
//        if (filled)
//            return shape(shape);
//        else
//            return wireShape(shape);
//    }
//
//    /**
//     * Draws a set of triangles, each with the specified color. There should be one 
//     * color for each triangle. This is much more efficient than drawing individual
//     * triangles.
//     */
//    public static Shape triangles (double[][] points, Color[] colors) {
//        return triangles(points, colors, true);
//    }
//
//    /**
//     * Draws a set of wireframetriangles, each with the specified color. There should be one 
//     * color for each triangle. This is much more efficient than drawing individual
//     * triangles.
//     */
//    public static Shape wireTriangles (double[][] points, Color[] colors) {
//        return triangles(points, colors, false);
//    }
//
//    private static Shape triangles (double[][] points, Color[] colors, boolean filled) {
//        int size = points.length;
//        Point3f[] coords = new Point3f[size*3];
//
//        for (int i = 0; i < size; i++) {
//            coords[3*i]   = new Point3f(createVector3f(points[i][0], points[i][1], points[i][2]));
//            coords[3*i+1] = new Point3f(createVector3f(points[i][3], points[i][4], points[i][5]));
//            coords[3*i+2] = new Point3f(createVector3f(points[i][6], points[i][7], points[i][8]));
//        }
//
//        GeometryArray geom = new TriangleArray(size*3, TriangleArray.COORDINATES | TriangleArray.COLOR_4);
//        geom.setCoordinates(0, coords);
//
//        for (int i = 0; i < colors.length; i++) {
//            geom.setColor(3 * i + 0, colors[i].getComponents(null));
//            geom.setColor(3 * i + 1, colors[i].getComponents(null));
//            geom.setColor(3 * i + 2, colors[i].getComponents(null));
//        }
//
//        GeometryInfo geoinfo = new GeometryInfo(geom);
//        NormalGenerator normalGenerator = new NormalGenerator();
//        normalGenerator.generateNormals(geoinfo);
//
//        Shape3D shape = createShape3D(geoinfo.getIndexedGeometryArray());
//
//        if (filled)
//            return shape(shape);
//        else
//            return wireShape(shape);
//    }
//
//    //*********************************************************************************************
//
//    /**
//     * Draws a 3D text object at (x, y, z). Uses the pen font, color, and transparency.
//     */
//    public static Shape text3D (double x, double y, double z, String text) {
//
//        return text3D(x, y, z, text, 0, 0, 0);
//    }
//
//    /**
//     * Draws a 3D text object at (x, y, z) with Euler rotation angles (xA, yA, zA).
//     * Uses the pen font, color, and transparency.
//     */
//    public static Shape text3D (double x, double y, double z, String text, double xA, double yA, double zA) {
//
//        Line2D.Double line = new Line2D.Double(0, 0, TEXT3D_DEPTH, 0);
//        FontExtrusion extrudePath = new FontExtrusion(line);
//        Font3D font3D = new Font3D(font, extrudePath);
//        Point3d pos = new Point3d(x, y, z);
//        Text3D t = new Text3D(font3D, text, createPoint3f(x, y, z));
//
//        // FIX THIS TO NOT HAVE SCALE INCLUDED
//        Transform3D shrinker = new Transform3D();
//        shrinker.setEuler(new Vector3d(xA, yA, zA));
//        shrinker.setTranslation(new Vector3d(x, y, z));
//        shrinker.setScale(TEXT3D_SHRINK_FACTOR);
//        Shape3D shape = createShape3D(t);
//        return shape(shape, true, shrinker, false);
//    }
//
//    //*********************************************************************************************
//    /**
//     * Constructs a Point3f array from the given coordinate arrays.
//     */
//    private static Point3f[] constructPoint3f (double[] x, double[] y, double[] z) {
//
//        int size = x.length;
//        Point3f[] coords = new Point3f[size];
//
//        for (int i = 0; i < size; i++)
//            coords[i] = new Point3f(createVector3f(x[i], y[i], z[i]));
//
//        return coords;
//    }
//
//    /**
//     * Draws a .ply file from a filename or website name.
//     */
//    private static Shape drawPLY (String filename, boolean colored) {
//
//        Scanner scanner = createScanner(filename);
//
//        int vertices = -1;
//        int triangles = -1;
//        int properties = -1;
//
//        while (true) {
//            String s = scanner.next();
//            if (s.equals("vertex"))
//                vertices = scanner.nextInt();
//            else if (s.equals("face"))
//                triangles = scanner.nextInt();
//            else if (s.equals("property")) {
//                properties++;
//                scanner.next();
//                scanner.next();
//            }
//            else if (s.equals("end_header"))
//                break;
//        }
//
//        System.out.println(vertices + " " + triangles + " " + properties);
//
//        if ((vertices == -1) || (triangles == -1) || (properties == -1))
//            throw new RuntimeException("Cannot read format of .ply file!");
//
//        double[][] parameters = new double[properties][vertices];
//
//        for (int i = 0; i < vertices; i++) {
//            if ((i % 10000) == 0)
//                System.out.println("vertex " + i);
//            for (int j = 0; j < properties; j++) {
//                parameters[j][i] = scanner.nextDouble();
//            }
//        }
//
//        double[][] points = new double[triangles][9];
//
//        for (int i = 0; i < triangles; i++) {
//            int edges = scanner.nextInt();
//            if (edges != 3) 
//                throw new RuntimeException("Only triangular faces supported!");
//
//            if ((i % 10000) == 0)
//                System.out.println("face " + i);
//
//            int index = scanner.nextInt();
//            points[i][0] = parameters[0][index];
//            points[i][1] = parameters[1][index];
//            points[i][2] = parameters[2][index];
//
//            index = scanner.nextInt();
//            points[i][3] = parameters[0][index];
//            points[i][4] = parameters[1][index];
//            points[i][5] = parameters[2][index];
//
//            index = scanner.nextInt();
//            points[i][6] = parameters[0][index];
//            points[i][7] = parameters[1][index];
//            points[i][8] = parameters[2][index];
//        }
//
//        return triangles(points);
//    }
//
//    private static Shape drawLWS (String filename) {
//
//        Lw3dLoader loader = new Lw3dLoader();
//        try {
//            BranchGroup bg = loader.load(filename).getSceneGroup();
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
//            bg.setCapability(BranchGroup.ALLOW_DETACH);
//
//            TransformGroup transGroup = new TransformGroup();
//            transGroup.addChild(bg);
//            BranchGroup bg2 = createBranchGroup();
//            bg2.addChild(transGroup);
//            offscreenGroup.addChild(bg2);
//            return new Shape(bg2, transGroup);
//        } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); }
//        return null;
//    }
//
//    private static Shape drawOBJ (String filename, boolean colored) {
//
//        int params = ObjectFile.RESIZE | Loader.LOAD_ALL;
//        ObjectFile loader = new ObjectFile(params);
//        try {
//            BranchGroup bg = loader.load(filename).getSceneGroup();
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
//            bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
//            bg.setCapability(BranchGroup.ALLOW_DETACH);
//
//            //System.out.println("Children: " + bg.numChildren());
//
//            for (int i = 0; i < bg.numChildren(); i++) {
//                Node child = bg.getChild(i);
//                if (child instanceof Shape3D) {
//                    Shape3D shape = (Shape3D) child;
//                    //System.out.println("shape3d");
//                    //                     Appearance ap = shape.getAppearance();
//                    //                     PolygonAttributes pa = ap.getPolygonAttributes();
//                    //                     if (pa == null) pa = new PolygonAttributes();
//                    //                     pa.setCullFace(PolygonAttributes.CULL_NONE);
//                    //                     ap.setPolygonAttributes(pa);
//                    //                     Material m = ap.getMaterial();
//                    //                     m.setSpecularColor(new Color3f(GRAY));
//                    //                     m.setShininess(64);
//                    if (colored)
//                        shape.setAppearance(createAppearance(null, true));
//                    else {
//                        Appearance ap = shape.getAppearance();
//                        PolygonAttributes pa = ap.getPolygonAttributes();
//                        if (pa == null) pa = new PolygonAttributes();
//                        pa.setCullFace(PolygonAttributes.CULL_NONE);
//                        ap.setPolygonAttributes(pa);
//                    }
//                    //for (int j = 0; j < shape.numGeometries(); j++) {
//                    //    Geometry g = shape.getGeometry(j);
//                    //    if (g instanceof GeometryArray) {
//                    //        GeometryArray ga = (GeometryArray) g;
//                    //System.out.println("GeometryArray");
//                    //System.out.println("format: " + ga.getVertexFormat());
//                    //float[] colors = ga.getInterleavedVertices();
//                    //for (int k = 0; k < colors.length; k++)
//                    //    System.out.println(colors[k]);
//                    //    }
//                    //}
//                }
//            }
//
//            TransformGroup transGroup = new TransformGroup();
//            transGroup.addChild(bg);
//            BranchGroup bg2 = createBranchGroup();
//            bg2.addChild(transGroup);
//            offscreenGroup.addChild(bg2);
//            return new Shape(bg2, transGroup);
//        } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); }
//        return null;
//    }
//
//    public static Shape model (String filename) {
//        return model(filename, false);
//    }
//
//    public static Shape coloredModel (String filename) {
//        return model(filename, true);
//    }
//
//    private static Shape model (String filename, boolean colored) {
//
//        if (filename == null) return null;
//        String suffix = filename.substring(filename.lastIndexOf('.') + 1);
//        String extension = suffix.toLowerCase();
//
//        if (suffix.equals("ply"))
//            return drawPLY(filename, colored);
//        else if (suffix.equals("obj"))
//            return drawOBJ(filename, colored);
//        //else if (suffix.equals("lws"))
//        //    return drawLWS(filename);
//        else
//            throw new RuntimeException("Format not supported!");
//    }
//
//    /**
//     * Draws a Java 3D Shape3D object and fills it in.
//     * 
//     * @param shape The Shape3D object to be drawn.
//     */
//    private static Shape shape (Shape3D shape) {
//        return shape(shape, true, null, false);
//    }
//
//    /**
//     * Draws a wireframe Java 3D Shape3D object and fills it in.
//     * 
//     * @param shape The Shape3D object to be drawn.
//     */
//    private static Shape wireShape (Shape3D shape) {
//        return shape(shape, false, null, false);
//    }
//
//    private static Shape customShape (Shape3D shape) {
//        return shape(shape, true, null, true);
//    }
//
//    private static Shape customWireShape (Shape3D shape) {
//        return shape(shape, false, null, true);
//    }
//
//    /**
//     * Draws a Java3D Shape3D object with the given properties.
//     * 
//     * @param polygonFill Polygon fill properties, specified by Java 3D.
//     */
//    private static Shape shape (Shape3D shape, boolean fill, Transform3D transform, boolean custom) {
//
//        Appearance ap;
//
//        if (custom) ap = createCustomAppearance(fill);
//        else        ap = createAppearance(null, fill);
//
//        shape.setAppearance(ap);
//
//        TransformGroup transGroup = new TransformGroup();
//        if (transform != null)
//            transGroup.setTransform(transform);
//
//        transGroup.addChild(shape);
//
//        BranchGroup bg = createBranchGroup();
//        bg.addChild(transGroup);
//        offscreenGroup.addChild(bg);
//        return new Shape(bg, transGroup);
//    }
//
//    /* ***************************************************************
//     *                 2D Overlay Drawing Methods                    *
//     *****************************************************************/
//
//    /**
//     * Draws one pixel at (x, y).
//     */
//    public static void overlayPixel (double x, double y) {
//        getGraphics2D(offscreenImage).fillRect(Math.round(scaleX(x)), Math.round(scaleY(y)), 1, 1);
//    }
//
//    /**
//     * Draws a point at (x, y).
//     */
//    public static void overlayPoint (double x, double y) {
//        float r = penRadius;
//        if (r <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).fill(new Ellipse2D.Double(scaleX(x) - r/2, scaleY(y) - r/2, r, r));
//    }
//
//    /**
//     * Draws a line from (x0, y0) to (x1, y1).
//     */
//    public static void overlayLine (double x0, double y0, double x1, double y1) {
//        getGraphics2D(offscreenImage).draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
//    }
//
//    /**
//     * Draws a circle of radius r, centered on (x, y).
//     */
//    public static void overlayCircle (double x, double y, double r) {
//
//        if (r < 0) throw new RuntimeException("circle radius can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*r);
//        double hs = factorY(2*r);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a filled circle of radius r, centered on (x, y).
//     */
//    public static void overlayFilledCircle (double x, double y, double r) {
//
//        if (r < 0) throw new RuntimeException("circle radius can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*r);
//        double hs = factorY(2*r);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws an ellipse with given semimajor and semiminor axes, centered on (x, y).
//     */
//    public static void overlayEllipse (double x, double y, double semiMajorAxis, double semiMinorAxis) {
//        if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
//        if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*semiMajorAxis);
//        double hs = factorY(2*semiMinorAxis);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a filled ellipse with given semimajor and semiminor axes, centered on (x, y).
//     */
//    public static void overlayFilledEllipse (double x, double y, double semiMajorAxis, double semiMinorAxis) {
//        if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
//        if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*semiMajorAxis);
//        double hs = factorY(2*semiMinorAxis);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees).
//     */
//    public static void overlayArc (double x, double y, double r, double angle1, double angle2) {
//        if (r < 0) throw new RuntimeException("arc radius can't be negative");
//        while (angle2 < angle1) angle2 += 360;
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*r);
//        double hs = factorY(2*r);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
//    }
//
//    /**
//     * Draws a square of side length 2r, centered on (x, y).
//     */
//    public static void overlaySquare (double x, double y, double r) {
//        if (r < 0) throw new RuntimeException("square side length can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*r);
//        double hs = factorY(2*r);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a filled square of side length 2r, centered on (x, y).
//     */
//    public static void overlayFilledSquare (double x, double y, double r) {
//        if (r < 0) throw new RuntimeException("square side length can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*r);
//        double hs = factorY(2*r);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a rectangle of given half width and half height, centered on (x, y).
//     */
//    public static void overlayRectangle (double x, double y, double halfWidth, double halfHeight) {
//        if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
//        if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*halfWidth);
//        double hs = factorY(2*halfHeight);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a filled rectangle of given half width and half height, centered on (x, y).
//     */
//    public static void overlayFilledRectangle (double x, double y, double halfWidth, double halfHeight) {
//        if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
//        if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(2*halfWidth);
//        double hs = factorY(2*halfHeight);
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else getGraphics2D(offscreenImage).fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
//    }
//
//    /**
//     * Draws a polygon with the given (x[i], y[i]) coordinates.
//     */
//    public static void overlayPolygon (double[] x, double[] y) {
//        int N = x.length;
//        GeneralPath path = new GeneralPath();
//        path.moveTo(scaleX(x[0]), scaleY(y[0]));
//        for (int i = 0; i < N; i++)
//            path.lineTo(scaleX(x[i]), scaleY(y[i]));
//        path.closePath();
//        getGraphics2D(offscreenImage).draw(path);
//    }
//
//    /**
//     * Draws a filled polygon with the given (x[i], y[i]) coordinates.
//     */
//    public static void overlayFilledPolygon (double[] x, double[] y) {
//        int N = x.length;
//        GeneralPath path = new GeneralPath();
//        path.moveTo(scaleX(x[0]), scaleY(y[0]));
//        for (int i = 0; i < N; i++)
//            path.lineTo(scaleX(x[i]), scaleY(y[i]));
//        path.closePath();
//        getGraphics2D(offscreenImage).fill(path);
//    }
//
//    /**
//     * Draws the given text as stationary on the window at (x, y).
//     * This is useful for titles and HUD-style text.
//     */
//    public static void overlayText (double x, double y, String text) {
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        FontMetrics metrics = graphics.getFontMetrics();
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = metrics.stringWidth(text);
//        int hs = metrics.getDescent();
//        graphics.drawString(text, (float) (xs - ws/2.0), (float) (ys + hs));
//    }
//
//    /**
//     * Writes the given text string in the current font, centered on (x, y) and
//     * rotated by the specified number of degrees.
//     */
//    public static void overlayText (double x, double y, String text, double degrees) {
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        FontMetrics metrics = graphics.getFontMetrics();
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = metrics.stringWidth(text);
//        int hs = metrics.getDescent();
//        graphics.rotate(Math.toRadians(-degrees), xs, ys);
//        graphics.drawString(text, (float) (xs - ws/2.0), (float) (ys + hs));
//        graphics.rotate(Math.toRadians(+degrees), xs, ys);
//    }    
//
//    /**
//     * Write the given text string in the current font, left-aligned at (x, y).
//     */
//    public static void overlayTextLeft (double x, double y, String text) {
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        FontMetrics metrics = graphics.getFontMetrics();
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = metrics.stringWidth(text);
//        int hs = metrics.getDescent();
//        graphics.drawString(text, (float) (xs), (float) (ys + hs));
//    }
//
//    /**
//     * Write the given text string in the current font, right-aligned at (x, y).
//     */
//    public static void overlayTextRight (double x, double y, String text) {
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        FontMetrics metrics = graphics.getFontMetrics();
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = metrics.stringWidth(text);
//        int hs = metrics.getDescent();
//        graphics.drawString(text, (float) (xs - ws), (float) (ys + hs));
//    }
//
//    /**
//     * Draws a picture (gif, jpg, or png) centered on (x, y).
//     */
//    public static void overlayPicture (double x, double y, String s) {
//        Image image = getImage(s);
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = image.getWidth(null);
//        int hs = image.getHeight(null);
//        if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
//        getGraphics2D(offscreenImage).drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
//    }
//
//    /**
//     * Draws a picture (gif, jpg, or png) centered on (x, y),
//     * rotated given number of degrees.
//     */
//    public static void overlayPicture (double x, double y, String s, double degrees) {
//        Image image = getImage(s);
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        int ws = image.getWidth(null);
//        int hs = image.getHeight(null);
//        if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
//
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        graphics.rotate(Math.toRadians(-degrees), xs, ys);
//        graphics.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
//        graphics.rotate(Math.toRadians(+degrees), xs, ys);
//    }
//
//    /**
//     * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h.
//     */
//    public static void overlayPicture (double x, double y, String s, double w, double h) {
//        Image image = getImage(s);
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        if (w < 0) throw new RuntimeException("width is negative: " + w);
//        if (h < 0) throw new RuntimeException("height is negative: " + h);
//        double ws = factorX(w);
//        double hs = factorY(h);
//        if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//        else {
//            getGraphics2D(offscreenImage).drawImage(image, (int) Math.round(xs - ws/2.0),
//                (int) Math.round(ys - hs/2.0),
//                (int) Math.round(ws),
//                (int) Math.round(hs), null);
//        }
//    }
//
//    /**
//     * Draw picture (gif, jpg, or png) centered on (x, y), rotated
//     * given number of degrees, rescaled to w-by-h.
//     */
//    public static void overlayPicture (double x, double y, String s, double w, double h, double degrees) {
//        Image image = getImage(s);
//        double xs = scaleX(x);
//        double ys = scaleY(y);
//        double ws = factorX(w);
//        double hs = factorY(h);
//        if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
//        if (ws <= 1 && hs <= 1) overlayPixel(x, y);
//
//        Graphics2D graphics = getGraphics2D(offscreenImage);
//        graphics.rotate(Math.toRadians(-degrees), xs, ys);
//        graphics.drawImage(image, (int) Math.round(xs - ws/2.0),
//            (int) Math.round(ys - hs/2.0),
//            (int) Math.round(ws),
//            (int) Math.round(hs), null);
//        graphics.rotate(Math.toRadians(+degrees), xs, ys);
//    }
//
//    /**
//     * Gets an image from the given filename.
//     */
//    private static Image getImage (String filename) {
//
//        // to read from file
//        ImageIcon icon = new ImageIcon(filename);
//
//        // try to read from URL
//        if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
//            try {
//                URL url = new URL(filename);
//                icon = new ImageIcon(url);
//            } catch (Exception e) { /* not a url */ }
//        }
//
//        // in case file is inside a .jar
//        if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
//            URL url = StdDraw3D.class.getResource(filename);
//            if (url == null) throw new RuntimeException("image " + filename + " not found");
//            icon = new ImageIcon(url);
//        }
//
//        return icon.getImage();
//    }
//
//    private static Graphics2D getGraphics2D (BufferedImage image) {
//
//        Graphics2D graphics = (Graphics2D) image.getGraphics();
//        graphics.setColor(penColor);
//        graphics.setFont(font);
//        BasicStroke stroke = new BasicStroke(penRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
//        graphics.setStroke(stroke);
//
//        //if (getAntiAliasing())
//        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//
//        return graphics;
//    }
//
//    private static void infoDisplay () {
//
//        if (!infoDisplay) {
//            infoImage = createBufferedImage();
//            return;
//        }
//
//        BufferedImage bi = createBufferedImage();
//        Graphics2D g = (Graphics2D) bi.getGraphics();
//        g.setFont(new Font("Courier", PLAIN, 11));
//        g.setStroke(new BasicStroke(
//                DEFAULT_PEN_RADIUS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
//
//        double center = (min + max) / 2;
//        double r  = zoom;
//        double b = zoom * 0.1f;
//
//        DecimalFormat df = new DecimalFormat(" 0.000;-0.000");
//
//        Vector3D pos = camera.getPosition();
//        String s = "(" + df.format(pos.x) + "," + df.format(pos.y) + "," + df.format(pos.z) + ")";
//        g.setColor(BLACK);
//        g.drawString("Position: " + s, 21, 26);
//        g.setColor(LIGHT_GRAY);
//        g.drawString("Position: " + s, 20, 25);
//        //Vector3D forward = getDirection();
//        //String s1 = "(" + df.format(forward.x) + "," + df.format(forward.y) + "," + df.format(forward.z) + ")";
//        //g.drawString("Direction: " + s1, scaleX(-r) + 20, scaleY(r) + 40);
//
//        Vector3D rot = camera.getOrientation();
//        String s2 = "(" + df.format(rot.x) + "," + df.format(rot.y) + "," + df.format(rot.z) + ")";
//        g.setColor(BLACK);
//        g.drawString("Rotation: " + s2, 21, 41);
//        g.setColor(LIGHT_GRAY);
//        g.drawString("Rotation: " + s2, 20, 40);
//
//        String mode;
//        if (cameraMode == ORBIT_MODE)        mode = "Camera: ORBIT_MODE";
//        else if (cameraMode == FPS_MODE)          mode = "Camera: FPS_MODE";
//        else if (cameraMode == AIRPLANE_MODE)     mode = "Camera: AIRPLANE_MODE";
//        else if (cameraMode == LOOK_MODE)         mode = "Camera: LOOK_MODE";
//        else if (cameraMode == FIXED_MODE)        mode = "Camera: FIXED_MODE";
//        else throw new RuntimeException("Unknown camera mode!");
//
//        g.setColor(BLACK);
//        g.drawString(mode, 21, 56);
//        g.setColor(LIGHT_GRAY);
//        g.drawString(mode, 20, 55);
//
//        double d = b / 4;
//        g.draw(new Line2D.Double(scaleX(d + center), scaleY(0 + center), scaleX(-d + center), scaleY(0 + center)));
//        g.draw(new Line2D.Double(scaleX(0 + center), scaleY(d + center), scaleX( 0 + center), scaleY(-d + center)));
//
//        infoImage = bi;
//    }
//    /* ***************************************************************
//     *                    Saving/Loading Methods                  *
//     *****************************************************************/
//
//    /**
//     * Saves to file - suffix must be png, jpg, or gif. gif??
//     * 
//     * @param filename The name of the file with one of the required suffixes.
//     */
//    public static void save (String filename) {
//
//        //canvas.setVisible(false);
//        int oldCameraMode = getCameraMode();
//        setCameraMode(FIXED_MODE);
//
//        GraphicsContext3D context = canvas.getGraphicsContext3D();
//
//        BufferedImage buf = createBufferedImage();
//        ImageComponent2D imageComp = new ImageComponent2D(ImageComponent.FORMAT_RGB, buf);
//        javax.media.j3d.Raster ras = new javax.media.j3d.Raster(
//                new Point3f(-1.0f,-1.0f,-1.0f), 
//                javax.media.j3d.Raster.RASTER_COLOR, 
//                0, 0, 
//                width, height, 
//                imageComp, null);
//
//        context.readRaster(ras);
//
//        BufferedImage image = (ras.getImage()).getImage();
//
//        File file = new File(filename);
//        String suffix = filename.substring(filename.lastIndexOf('.') + 1);
//
//        String extension = suffix.toLowerCase();
//
//        // png files
//        if (extension.equals("png")) {
//            try { ImageIO.write(image, suffix, file); }
//            catch (IOException e) { e.printStackTrace(); }
//        }
//
//        // need to change from ARGB to RGB for jpeg
//        // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727
//        else if (extension.equals("jpg")) {
//            WritableRaster raster = image.getRaster();
//            WritableRaster newRaster;
//            newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
//            BufferedImage rgbBuffer = new BufferedImage(image.getColorModel(), newRaster, false,  null);
//            try { ImageIO.write(rgbBuffer, suffix, file); }
//            catch (IOException e) { e.printStackTrace(); }
//        }
//        else {
//            System.out.println("Invalid image file type: " + suffix);
//        }
//
//        setCameraMode(oldCameraMode);
//        //canvas.setVisible(true);
//    }
//
//    public static void saveScene3D (String filename) {
//
//        File file = new File(filename);
//        //System.out.println("on: " + onscreenGroup.numChildren() + " off: " + offscreenGroup.numChildren());
//
//        try {
//            SceneGraphFileWriter writer = new SceneGraphFileWriter(file, universe, false, "3D scene saved from StdDraw3D.", null);
//            writer.writeBranchGraph(offscreenGroup);
//            writer.close();
//            System.out.println("Scene successfully written to " + filename + "!");
//        }
//        catch (IOException ioe)                  { ioe.printStackTrace(); } 
//        catch (UnsupportedUniverseException uue) { uue.printStackTrace(); }  
//        //catch (NamedObjectException noe)         { noe.printStackTrace(); } 
//    }
//
//    public static void loadScene3D (String filename) {
//
//        File file = new File(filename);
//
//        try {
//            SceneGraphFileReader reader = new SceneGraphFileReader(file);
//            System.out.println("Branch graph count = " + reader.getBranchGraphCount());
//            //BranchGroup[] bgs = reader.readAllBranchGraphs();
//            //BranchGroup bg = (BranchGroup) reader.getNamedObject("rendered");
//            BranchGroup bg = reader.readBranchGraph(0)[0];
//            offscreenGroup = bg;
//            //reader.dereferenceBranchGraph(offscreenGroup);
//            System.out.println("Scene successfully loaded from " + filename + "!");
//        } 
//        catch (IOException ioe)                  { ioe.printStackTrace(); } 
//        //catch (ObjectNotLoadedException onle)    { onle.printStackTrace(); }  
//        //catch (NamedObjectException noe)         { noe.printStackTrace(); } 
//    }
//
//    /* ***************************************************************
//     *                    Shape Methods                              *
//     *****************************************************************/
//
//    /**
//     * Combines any number of shapes into one shape and returns it.
//     */
//    public static Shape combine (Shape... shapes) {
//
//        BranchGroup    combinedGroup = createBranchGroup();
//        TransformGroup combinedTransform = new TransformGroup();
//
//        for (int i = 0; i < shapes.length; i++) {
//            BranchGroup bg = shapes[i].bg;
//            TransformGroup tg = shapes[i].tg;
//
//            offscreenGroup.removeChild(bg);
//            onscreenGroup.removeChild(bg);
//
//            bg.removeChild(tg);
//            combinedTransform.addChild(shapes[i].tg);
//        }
//
//        combinedGroup.addChild(combinedTransform);
//        offscreenGroup.addChild(combinedGroup);
//        return new Shape(combinedGroup, combinedTransform);
//    }
//
//    /**
//     * Returns an identical copy of a Shape that can be controlled independently.
//     * Much more efficient than redrawing a specific shape or model.
//     */
//    public static Shape copy (Shape shape) {
//        TransformGroup tg = shape.tg;
//        BranchGroup bg = shape.bg;
//        TransformGroup tg2 = (TransformGroup)tg.cloneTree();
//        BranchGroup bg2 = createBranchGroup();
//
//        bg2.addChild(tg2);
//        offscreenGroup.addChild(bg2);
//
//        return new Shape(bg2, tg2);
//    }
//
//    private static class Transformable {
//
//        private TransformGroup tg;
//
//        private Transformable (TransformGroup tg0) {
//            this.tg = tg0;
//        }
//
//        private Transform3D getTransform () {
//            Transform3D t = new Transform3D();
//            tg.getTransform(t);
//            return t;
//        }
//
//        private void setTransform (Transform3D t) {
//            tg.setTransform(t);
//        }
//
//        private Vector3D relToAbs (Vector3D r) {
//            Transform3D t = getTransform();
//
//            Matrix3d m = new Matrix3d();
//            t.get(m);
//            Vector3d zero = new Vector3d(0, 0, 0);
//            Transform3D rotation = new Transform3D(m, zero, 1.0);
//            Vector3f vec = createVector3f(r);
//            rotation.transform(vec);
//
//            return new Vector3D(vec);
//        }
//
//        private Vector3D absToRel (Vector3D r) {
//            Transform3D t = getTransform();
//
//            Matrix3d m = new Matrix3d();
//            t.get(m);
//            Vector3d zero = new Vector3d(0, 0, 0);
//            Transform3D rotation = new Transform3D(m, zero, 1.0);
//            Vector3f vec = createVector3f(r);
//            rotation.invert();
//            rotation.transform(vec);
//
//            return new Vector3D(vec);
//        }
//
//        private void rotateQuat (double x, double y, double z, double w) {
//            rotateQuat(new Quat4d(x, y, z, w));
//        }
//
//        private void rotateQuat (Quat4d quat) {
//            Transform3D t = getTransform();
//
//            Transform3D t1 = new Transform3D();
//            t1.setRotation(quat);
//            t.mul(t1);
//
//            setTransform(t);
//        }
//
//        public void setQuaternion (double x, double y, double z, double w) {
//            setQuaternion(new Quat4d(x, y, z, w));
//        }
//
//        public void setQuaternion (Quat4d quat) {
//            Transform3D t = getTransform();
//
//            t.setRotation(quat);
//
//            setTransform(t);
//        }
//
//        public Quat4d getQuaternion () {
//            Transform3D t = getTransform();
//
//            Matrix3d m = new Matrix3d();
//            t.get(m);
//
//            double w = Math.sqrt(Math.max(0, 1 + m.m00 + m.m11 + m.m22))/2;
//            double x = Math.sqrt(Math.max(0, 1 + m.m00 - m.m11 - m.m22))/2; 
//            double y = Math.sqrt(Math.max(0, 1 - m.m00 + m.m11 - m.m22))/2; 
//            double z = Math.sqrt(Math.max(0, 1 - m.m00 - m.m11 + m.m22))/2; 
//            if (m.m21 - m.m12 < 0) x = -x;
//            if (m.m02 - m.m20 < 0) y = -y;
//            if (m.m10 - m.m01 < 0) z = -z;
//
//            return new Quat4d(x, y, z, w);
//        }
//
//        private void orientAxis (Vector3D axis, double angle) {
//            Transform3D t = getTransform();
//
//            AxisAngle4d aa = new AxisAngle4d(axis.x, axis.y, axis.z, Math.toRadians(angle));
//            t.setRotation(aa);
//
//            setTransform(t);
//        }
//
//        public void rotateAxis (Vector3D axis, double angle) {
//            if(angle == 0) return;
//            Transform3D t = getTransform();
//
//            Vector3D aRel = absToRel(axis);
//            AxisAngle4d aa = new AxisAngle4d(aRel.x, aRel.y, aRel.z, Math.toRadians(angle));
//            Transform3D t1 = new Transform3D();
//            t1.setRotation(aa);
//            t.mul(t1);
//
//            setTransform(t);
//        }
//
//        public void move (double xShift, double yShift, double zShift) {
//            move(new Vector3D(xShift, yShift, zShift));
//        }
//
//        public void move (Vector3D shift) {
//            Transform3D t = getTransform();
//
//            Vector3f r = new Vector3f();
//            t.get(r);
//            r.add(createVector3f(shift));
//            t.setTranslation(r);
//
//            setTransform(t);
//        }
//
//        public void moveRelative (double right, double up, double forward) {
//            moveRelative(new Vector3D(right, up, forward));
//        }
//
//        public void moveRelative (Vector3D move) {
//            move(relToAbs(move.times(1, 1, -1)));
//        }
//
//        public void setPosition (double x, double y, double z) {
//            setPosition(new Vector3D(x, y, z));
//        }
//
//        public void setPosition (Vector3D pos) {
//            Transform3D t = getTransform();
//
//            t.setTranslation(createVector3f(pos));
//
//            setTransform(t);
//        }
//
//        public Vector3D getPosition () {
//            Transform3D t = getTransform();
//            Vector3d r = new Vector3d();
//            t.get(r);
//            return new Vector3D(r);
//        }
//
//        public void rotate (double xAngle, double yAngle, double zAngle) {
//            rotate(new Vector3D(xAngle, yAngle, zAngle));
//        }
//
//        public void rotate (Vector3D angles) {
//            Transform3D t = getTransform();
//
//            Transform3D tX = new Transform3D();
//            Transform3D tY = new Transform3D();
//            Transform3D tZ = new Transform3D();
//
//            Vector3D xR = absToRel(xAxis);
//            Vector3D yR = absToRel(yAxis);
//            Vector3D zR = absToRel(zAxis);
//
//            Vector3D radians = angles.times(Math.PI / 180.);
//            tX.setRotation(new AxisAngle4d(xR.x, xR.y, xR.z, radians.x));
//            tY.setRotation(new AxisAngle4d(yR.x, yR.y, yR.z, radians.y));
//            tZ.setRotation(new AxisAngle4d(zR.x, zR.y, zR.z, radians.z));
//
//            t.mul(tX);
//            t.mul(tY);
//            t.mul(tZ);
//
//            setTransform(t);
//        }
//
//        public void rotateRelative (double pitch, double yaw, double roll) {
//            rotateRelative(new Vector3D(pitch, yaw, roll));
//        }
//
//        public void rotateRelative (Vector3D angles) {
//            Transform3D t = getTransform();
//
//            Transform3D tX = new Transform3D();
//            Transform3D tY = new Transform3D();
//            Transform3D tZ = new Transform3D();
//
//            Vector3D radians = angles.times(Math.PI / 180.);
//            tX.setRotation(new AxisAngle4d(1, 0, 0, radians.x));
//            tY.setRotation(new AxisAngle4d(0, 1, 0, radians.y));
//            tZ.setRotation(new AxisAngle4d(0, 0, 1, radians.z));
//
//            t.mul(tX);
//            t.mul(tY);
//            t.mul(tZ);
//
//            setTransform(t);
//        }
//
//        public void setOrientation (double xAngle, double yAngle, double zAngle) {
//            setOrientation(new Vector3D(xAngle, yAngle, zAngle));
//        }
//
//        public void setOrientation (Vector3D angles) {
//            if (Math.abs(angles.y) == 90) 
//                System.err.println("Gimbal lock when the y-angle is vertical!");
//
//            Transform3D t = getTransform();
//
//            Vector3D radians = angles.times(Math.PI / 180.);
//            Transform3D t1 = new Transform3D();
//            t1.setEuler(createVector3d(radians));
//            Vector3d r = new Vector3d();
//            t.get(r);
//            t1.setTranslation(r);
//            t1.setScale(t.getScale());
//
//            setTransform(t1);
//        }
//
//        public Vector3D getOrientation () {
//            Transform3D t = getTransform();
//
//            Matrix3d mat = new Matrix3d();
//            t.get(mat);
//
//            double xA, yA, zA;
//
//            yA = -Math.asin(mat.m20);
//            double C = Math.cos(yA);
//            if ( Math.abs(C) > 0.005 ) {
//                xA  = -Math.atan2( -mat.m21 / C, mat.m22 / C );
//                zA  = -Math.atan2( -mat.m10 / C, mat.m00 / C );
//            } else {
//                xA  = 0;        
//                zA  = -Math.atan2( mat.m01, mat.m11 );
//            }
//
//            xA = Math.toDegrees(xA);
//            yA = Math.toDegrees(yA);
//            zA = Math.toDegrees(zA);
//
//            /* return only positive angles in [0,360] */
//            if (xA < 0) xA += 360;
//            if (yA < 0) yA += 360;
//            if (zA < 0) zA += 360;
//
//            return new Vector3D(xA, yA, zA);
//        }
//
//        public void lookAt (Vector3D center) {
//            lookAt(center, yAxis);    
//        }
//
//        public void lookAt (Vector3D center, Vector3D up) {
//            Transform3D t = getTransform();
//
//            Transform3D t2 = new Transform3D(t);
//
//            Vector3f translation = new Vector3f();
//            t2.get(translation);
//
//            Vector3d scales = new Vector3d();
//            t2.getScale(scales);
//
//            Point3d trans = new Point3d(translation);
//            Point3d c = new Point3d(center.x, center.y, center.z);
//            Vector3d u = createVector3d(up);
//            t2.lookAt(trans, c, u);
//
//            try { 
//                t2.invert(); 
//                t2.setScale(scales);
//                setTransform(t2);
//            } catch (SingularMatrixException sme) { System.out.println("Singular matrix, bad lookAt()!"); }
//
//        }
//
//        public void setDirection (Vector3D direction) {
//            setDirection(direction, yAxis);
//        }
//
//        public void setDirection (Vector3D direction, Vector3D up) {
//            Vector3D center = getPosition().plus(direction);
//            lookAt(center, up);
//        }
//
//        public Vector3D getDirection () {
//            return relToAbs(zAxis.times(-1)).direction();
//        }
//        
//        private void match (Transformable s) {
//            setOrientation(s.getOrientation());
//            setPosition(s.getPosition());
//        }
//    }
//
//    public static Vector3D getCameraPosition () {
//        return camera.getPosition();
//    }
//
//    public static Vector3D getCameraOrientation () {
//        return camera.getOrientation();
//    }
//
//    public static Vector3D getCameraDirection () {
//        return camera.getDirection();
//    }
//
//    public static void setCameraPosition (double x, double y, double z) {
//        setCameraPosition(new Vector3D(x, y, z));
//    }
//
//    public static void setCameraPosition (Vector3D pos) {
//        camera.setPosition(pos);
//    }
//
//    public static void setCameraOrientation (double xAngle, double yAngle, double zAngle) {
//        setCameraOrientation(new Vector3D(xAngle, yAngle, zAngle));
//    }
//
//    public static void setCameraOrientation (Vector3D angles) {
//        camera.setOrientation(angles);
//    }
//
//    public static void setCameraDirection (double x, double y, double z) {
//        setCameraDirection(new Vector3D(x, y, z));
//    }
//
//    public static void setCameraDirection (Vector3D direction) {
//        camera.setDirection(direction);
//    }
//
//    public static void setCamera (double xPos, double yPos, double zPos, double xAngle, double yAngle, double zAngle) {
//        camera.setPosition(xPos, yPos, zPos);
//        camera.setOrientation(xAngle, yAngle, zAngle);
//    }
//
//    public static void setCamera (Vector3D position, Vector3D orientation) {
//        camera.setPosition(position);
//        camera.setOrientation(orientation);
//    }
//
//    public static Camera camera () {
//        return camera;
//    }
//
//    public static class Camera extends Transformable {
//
//        private TransformGroup tg;
//        private Shape pair;
//
//        private Camera (TransformGroup tg) {
//            super(tg);
//            this.tg = tg;
//        }
//
//        public void match (Shape s) {
//            super.match(s);
//        }
//
//        public void pair (Shape s) {
//            pair = s;
//        }
//
//        @Override
//		public void moveRelative (Vector3D move) {
//            if ((view.getProjectionPolicy() == View.PARALLEL_PROJECTION)) {
//                setScreenScale(view.getScreenScale() * (1 + move.z / zoom));
//                super.move(super.relToAbs(move).times(1, 1, 0));
//            } else super.move(super.relToAbs(move.times(1, 1, -1)));
//        }
//
//        public void rotateFPS (Vector3D angles) {
//            rotateFPS(angles.x, angles.y, angles.z);
//        }
//
//        public void rotateFPS (double xAngle, double yAngle, double zAngle) {
//
//            double xA = Math.toRadians(xAngle);
//            double yA = Math.toRadians(yAngle);
//            double zA = Math.toRadians(zAngle);
//
//            Vector3D shift = super.relToAbs(new Vector3D(-yA, xA, zA));
//            Vector3D dir = super.getDirection().plus(shift);
//            double angle = dir.angle(yAxis);
//            if (angle > 90) angle = 180 - angle;
//            if (angle < 5) return;
//            super.setDirection(super.getDirection().plus(shift));
//        }
//
//    }
//
//    public static class Shape extends Transformable {
//
//        private BranchGroup bg;
//        private TransformGroup tg;
//
//        private Shape (BranchGroup bg, TransformGroup tg) {
//            super(tg);
//            this.bg = bg;
//            this.tg = tg;
//            tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
//            tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
//        }
//
//        public void scale (double scale) {
//            Transform3D t = super.getTransform();
//
//            t.setScale(t.getScale() * scale);
//
//            super.setTransform(t);
//        }
//
//        //         public void scale (double xScale, double yScale, double zScale) {
//        //             scale(new Vector3D(xScale, yScale, zScale));
//        //         }
//        //         
//        //         public void scale (Vector3D scales) {
//        //             Transform3D t = getTransform();
//        //             throw new UnsupportedOperationException("Doesn't work with rotation!");
//        //             setTransform(t);
//        //         }
//
//        public void hide () {
//            offscreenGroup.removeChild(bg);
//            onscreenGroup.removeChild(bg);
//        }
//
//        public void unhide () {
//            hide();
//            offscreenGroup.addChild(bg);
//        }
//        
//        public void match (Shape s) {
//            super.match(s);
//        }
//        
//        public void match (Camera c) {
//            super.match(c);
//        }
//        
//        public void setColor (Color c) {
//            setColor(tg, c);
//        }
//
//        public void setColor (Color c, int alpha) {
//            setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha));
//        }
//
//        private void setColor (Group g, Color c) {
//            for (int i = 0; i < g.numChildren(); i++) {
//                Node child = g.getChild(i);
//                if (child instanceof Shape3D) {
//                    Shape3D shape = (Shape3D)child;
//                    Appearance ap = shape.getAppearance();
//                    setColor(ap, c);
//                } else if (child instanceof Primitive) {
//                    Primitive primitive = (Primitive)child;
//                    Appearance ap = primitive.getAppearance();
//                    setColor(ap, c);
//                } else if (child instanceof Group) {
//                    setColor((Group)child, c);
//                }
//            }
//        }
//
//        private void setColor (Appearance ap, Color c) {
//            Material m = ap.getMaterial();
//            m.setAmbientColor(new Color3f(c));
//            m.setDiffuseColor(new Color3f(c));
//
//            float alpha = ((float)c.getAlpha()) / 255;
//            if (alpha < 1.0) {
//                TransparencyAttributes t = new TransparencyAttributes();
//                t.setTransparencyMode(TransparencyAttributes.BLENDED);
//                t.setTransparency(1 - alpha);
//                ap.setTransparencyAttributes(t);
//            } else ap.setTransparencyAttributes(null);
//        }
//    }
//
//    public static class Light extends Transformable {
//
//        javax.media.j3d.Light light;
//        BranchGroup bg;
//
//        private Light (BranchGroup bg, TransformGroup tg, javax.media.j3d.Light light) {
//            super(tg);
//            this.light = light;
//            this.bg = bg;
//        }
//        
//        public void hide () {
//            light.setEnable(false);
//        }
//
//        public void unhide () {
//            light.setEnable(true);
//        }
//
//        public void match (Shape s) {
//            super.match(s);
//        }
//        
//        public void match (Camera c) {
//            super.match(c);
//        }
//        
//        public void setColor (Color col) {
//            light.setColor(new Color3f(col));
//        }
//
//        public void scalePower (double power) {
//
//            if (light instanceof PointLight) {
//                
//                double attenuationScale = 1.0 / (0.999 * power + 0.001);
//                
//                PointLight pl = (PointLight)light;
//                Point3f attenuation = new Point3f();
//                pl.getAttenuation(attenuation);
//                attenuation.y *= attenuationScale;
//                attenuation.z *= attenuationScale * attenuationScale;
//
//                pl.setAttenuation(attenuation);
//            } else {
//                System.err.println("Can only scale power for point lights!");
//            }
//        }
//    }
//
//    /******************************************************************************
//     * An immutable three-dimensional vector class with useful vector operations.
//     * 
//     * @author Hayk Martirosyan <hmartiro@princeton.edu>
//     * @since 2011.0310
//     * @version 1.0
//     ****************************************************************************/
//    public static class Vector3D { 
//
//        /** X-coordinate - immutable but directly accessible. */
//        public final double x;
//
//        /** Y-coordinate - immutable but directly accessible. */
//        public final double y;
//
//        /** Z-coordinate - immutable but directly accessible. */
//        public final double z;
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Initializes to zero vector.
//         */
//        public Vector3D () {
//
//            this.x = 0;
//            this.y = 0;
//            this.z = 0;
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Initializes to the given coordinates.
//         * 
//         * @param x X-coordinate
//         * @param y Y-coordinate
//         * @param z Z-coordinate
//         */
//        public Vector3D (double x, double y, double z) {
//
//            this.x = x;
//            this.y = y;
//            this.z = z;
//        }
//
//        /**
//         * Initializes to the given coordinates.
//         * 
//         * @param c Array length 3 of coordinates (x, y, z,).
//         */
//        //--------------------------------------------------------------------------
//
//        public Vector3D (double[] c) {
//
//            if (c.length != 3)
//                throw new RuntimeException("Incorrect number of dimensions!");
//            this.x = c[0];
//            this.y = c[1];
//            this.z = c[2];
//        }
//
//        //--------------------------------------------------------------------------
//
//        private Vector3D (Vector3d v) {
//
//            this.x = v.x;
//            this.y = v.y;
//            this.z = v.z;
//        }
//
//        //--------------------------------------------------------------------------
//
//        private Vector3D (Vector3f v) {
//
//            this.x = v.x;
//            this.y = v.y;
//            this.z = v.z;
//        }
//
//        //--------------------------------------------------------------------------
//
//        private Vector3D (Point3d p) {
//
//            this.x = p.x;
//            this.y = p.y;
//            this.z = p.z;
//
//        }
//
//        //--------------------------------------------------------------------------
//
//        private Vector3D (Point3f p) {
//
//            this.x = p.x;
//            this.y = p.y;
//            this.z = p.z;
//
//        }
//
//        //--------------------------------------------------------------------------
//
//        public Vector3D (double min, double max) {
//
//            this.x = min + Math.random() * (max - min);
//            this.y = min + Math.random() * (max - min);
//            this.z = min + Math.random() * (max - min);
//        }
//
//        //--------------------------------------------------------------------------
//        /**
//         * Returns the dot product of this and that.
//         * 
//         * @param that Vector to dot with
//         * @return This dot that
//         */
//        public double dot (Vector3D that) {
//
//            return (this.x * that.x + this.y * that.y + this.z * that.z);
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the magnitude of this vector.
//         * 
//         * @return Magnitude of vector
//         */
//        public double mag () {
//
//            return Math.sqrt(this.dot(this));
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the smallest angle between this and that vector, in DEGREES.
//         * 
//         * @return Angle between the vectors, in DEGREES
//         */
//        public double angle (Vector3D that) {
//            return Math.toDegrees(Math.acos(this.dot(that)/(this.mag() * that.mag())));
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the Euclidian distance between this and that.
//         * 
//         * @param that Vector to compute distance between
//         * @return Distance between this and that
//         */
//        public double distanceTo (Vector3D that) {
//            return this.minus(that).mag();
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the sum of this and that vector.
//         * 
//         * @param that Vector to compute sum with
//         * @return This plus that
//         */
//        public Vector3D plus (Vector3D that) {
//
//            double cx = this.x + that.x;
//            double cy = this.y + that.y;
//            double cz = this.z + that.z;
//            Vector3D c = new Vector3D(cx, cy, cz);
//            return c;
//        }
//
//        public Vector3D plus (double x, double y, double z) {
//            double cx = this.x + x;
//            double cy = this.y + y;
//            double cz = this.z + z;
//            return new Vector3D(cx, cy, cz);
//        }
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the difference of this and that vector.
//         * 
//         * @param that Vector to compute difference with
//         * @return This minus that
//         */
//        public Vector3D minus (Vector3D that) {
//
//            double cx = this.x - that.x;
//            double cy = this.y - that.y;
//            double cz = this.z - that.z;
//            Vector3D c = new Vector3D(cx, cy, cz);
//            return c;
//        }
//
//        public Vector3D minus (double x, double y, double z) {
//            double cx = this.x - x;
//            double cy = this.y - y;
//            double cz = this.z - z;
//            return new Vector3D(cx, cy, cz);
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the product of this vector and the scalar a.
//         * 
//         * @param a Scalar to multiply by
//         * @return (this.x * a, this.y * a, this.z * a)
//         */
//        public Vector3D times (double a) {
//
//            double cx = this.x * a;
//            double cy = this.y * a;
//            double cz = this.z * a;
//            Vector3D c = new Vector3D(cx, cy, cz);
//            return c;
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the result (this.x * a, this.y * b, this.z * c).
//         * 
//         * @param a Scalar to multiply x by
//         * @param b Scalar to multiply y by
//         * @param c Scalar to multiply z by
//         * @return (this.x * a, this.y * b, this.z * c)
//         */
//        public Vector3D times (double a, double b, double c) {
//
//            double vx = this.x * a;
//            double vy = this.y * b;
//            double vz = this.z * c;
//            Vector3D v = new Vector3D(vx, vy, vz);
//            return v;
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the unit vector in the direction of this vector.
//         * 
//         * @return Unit vector with direction of this vector
//         */
//        public Vector3D direction () {
//
//            if (this.mag() == 0.0) throw new RuntimeException("Zero-vector has no direction");
//            return this.times(1.0 / this.mag());
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the projection of this onto the given line.
//         * 
//         * @param line Direction to project this vector onto
//         * @return This projected onto line
//         */
//        public Vector3D proj (Vector3D line) {
//
//            Vector3D normal = line.direction();
//
//            return normal.times(this.dot(normal));
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns the cross product of this vector with that vector.
//         * 
//         * @return This cross that
//         */
//        public Vector3D cross (Vector3D that) {
//
//            Vector3D a = this;
//            Vector3D b = that;
//
//            return new Vector3D(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Reflects this vector across the direction given by line.
//         * 
//         * @param line Direction to reflect this vector over
//         * @return This reflected over line
//         */
//        public Vector3D reflect (Vector3D line) {
//
//            return this.proj(line).times(2).minus(this);
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Returns a string representation of this vector.
//         * 
//         * @return "( this.x, this.y, this.z)"
//         */
//        @Override
//		public String toString() {
//
//            DecimalFormat df = new DecimalFormat("0.000000");
//            return ("( " + df.format(this.x) + ", " + df.format(this.y) + ", " + df.format(this.z) + " )");
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Draws this vector as a point from the origin to System.out.
//         */
//        public void draw () {
//
//            StdDraw3D.sphere(this.x, this.y, this.z, 0.01);
//        }
//
//        //--------------------------------------------------------------------------
//
//        /**
//         * Draws a line representation of this vector, from the given origin.
//         * 
//         * @param origin Origin point to draw from
//         */
//        public void drawLine (Vector3D origin) {
//
//            Vector3D end = this.plus(origin);
//
//            StdDraw3D.line(origin.x, origin.y, origin.z, end.x, end.y, end.z);
//        } 
//    }
//    
//  public static void main (String[] args) {
//    
//  // Turns off the default info HUD display.
//    StdDraw3D.setInfoDisplay(false);
//    
//    // Draws the white square border.
//    StdDraw3D.setPenColor(StdDraw3D.WHITE);
//    StdDraw3D.overlaySquare(0, 0, 0.98);
//    
//    // Draws the two red circles.
//    StdDraw3D.setPenRadius(30);
//    StdDraw3D.setPenColor(StdDraw3D.RED, 220);
//    StdDraw3D.overlayCircle(0, 0, 0.8);
//    StdDraw3D.setPenColor(StdDraw3D.RED, 220);
//    StdDraw3D.overlayCircle(0, 0, 0.6);
//    
//    // Draws the information text.
//    StdDraw3D.setPenColor(StdDraw3D.WHITE);
//    StdDraw3D.overlayText(0, 0.91, "Standard Draw 3D - Test Program");
//    StdDraw3D.overlayText(0, -0.95, "You should see rotating text. Drag the mouse to orbit.");
//    
//    // Creates the 3D text object and centers it.
//    StdDraw3D.setPenColor(StdDraw3D.YELLOW);
//    StdDraw3D.setFont("Arial", StdDraw3D.BOLD, 16);
//    StdDraw3D.Shape text = StdDraw3D.text3D(0, 0, 0, "StdDraw3D");
//    text.scale(2.5);
//    text.move(-0.5, -0.1, 0);
//    text = StdDraw3D.combine(text);
//    
//    while (true) {
//    
//          // Rotates the 3D text by 1.2 degrees along the y-axis.
//          text.rotate(0, 1.2, 0);
//          
//          // Shows the frame for 20 milliseconds.
//          StdDraw3D.show(20);
//      }
//  }
}
