All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.princeton.cs.introcs.StdDraw3D Maven / Gradle / Ivy

Go to download

Standard input and output libraries from Princeton's "Introduction to Programming in Java" textbook.

The newest version!
package edu.princeton.cs.introcs;

/******************************************************************************
 * StdDraw3D.java
 * Hayk Martirosyan
 ******************************************************************************
 * 3D Drawing Library
 *
 * Standard Draw 3D is a Java library with the express goal of making it
 * simple to create three-dimensional models, simulations, and games.
 *
 * Introductory Tutorial:
 * http://introcs.cs.princeton.edu/java/stddraw3d
 *
 * Reference manual:
 * http://introcs.cs.princeton.edu/java/stddraw3d-manual.html
 *
 * NOTE: The code below is only partially documented. Refer to the
 * reference manual for complete documentation.
 *
 *****************************************************************************/

// Native Java libraries.
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;

// Java3D libraries.
import javax.vecmath.*;
import javax.media.j3d.*;
import com.sun.j3d.utils.image.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.scenegraph.io.*;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.loaders.lw3d.Lw3dLoader;
import com.sun.j3d.loaders.Loader;
 
/**
 * Standard Draw 3D is a Java library with the express goal of making it
 * simple to create three-dimensional models, simulations, and games.
 * Here is a StdDraw3D tutorial
 * and the
 * StdDraw3D reference manual.
 *
 *  @author Hayk Martirosyan
 */

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;

    // 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;

    // 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;

    // Current background color.
    private static Color bgColor;
    
    // 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 keysDown = new TreeSet();
    private static LinkedList keysTyped = new LinkedList();

    // 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;

    // Pauses the renderer
    private static boolean showedOnce = true;
    private static boolean renderedOnce = 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 =  0.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 = 0.9;
    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 double DEFAULT_PEN_RADIUS = 0.002;
    private static final Color DEFAULT_PEN_COLOR  = StdDraw3D.WHITE;

    // Default background color.
    private static final Color DEFAULT_BGCOLOR    = StdDraw3D.BLACK;
    
    // 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();

        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);
        
        setBackground(DEFAULT_BGCOLOR);
        
        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);

/*                 while (!showedOnce) {
 
                    try { Thread.currentThread().sleep(0); }
                    catch (InterruptedException e) { System.out.println("Error sleeping"); }
                }
                showedOnce = false; */

                Thread.yield();
            }

            /*public void postSwap () {
            while (!showedOnce) {

            try { Thread.currentThread().sleep(5); }
            catch (InterruptedException e) { System.out.println("Error sleeping"); }
            }
            //if (!showedOnce) {
            //    try { Thread.currentThread().sleep(30); }
            //    catch (InterruptedException e) { System.out.println("Error sleeping"); }
            //}
            //StdOut.println("showed once is false, rendered once");
            showedOnce = false;
            //renderedOnce = true;

            //Thread.yield();
            }*/
        };

        canvas.addKeyListener(std);
        canvas.addMouseListener(std);
        canvas.addMouseMotionListener(std);
        canvas.addMouseWheelListener(std);

        canvas.setSize(width, height);
        p.add(canvas, gbc);

        canvasPanel = p;

        //canvas.stopRenderer();
    }

    /**
     * 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");
        // Leaving out graphics menu for now!!
        //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;
    }

    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
     */
    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
     */
    public void componentHidden(ComponentEvent e) { keysDown = new TreeSet(); }

    /** 
     * This method cannot be called directly.
     * @deprecated
     */
    public void componentMoved(ComponentEvent e) { keysDown = new TreeSet(); }

    /** 
     * This method cannot be called directly.
     * @deprecated
     */
    public void componentShown(ComponentEvent e) { keysDown = new TreeSet(); }

    /** 
     * This method cannot be called directly.
     * @deprecated
     */
    public void componentResized(ComponentEvent e) { keysDown = new TreeSet(); }

    /** 
     * This method cannot be called directly.
     * @deprecated
     */
    public void windowGainedFocus(WindowEvent e) { keysDown = new TreeSet(); }

    /** 
     * This method cannot be called directly.
     * @deprecated
     */
    public void windowLostFocus(WindowEvent e) { keysDown = new TreeSet(); }

    /**
     * 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 * (2 + Math.sqrt(2)));

        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 () { setPenRadius(DEFAULT_PEN_RADIUS); }

    /**
     * Sets the pen radius to the given value.
     * 
     * @param r The pen radius.
     */
    public static void  setPenRadius (double r) { penRadius = (float)r * 500; }

    /**
     * Gets the current pen radius.
     * 
     * @return The current pen radius.
     */
    public static float getPenRadius () { return penRadius/500f; }

    /**
     * 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; }
    
    /**
     * 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. 
* --------------------------------------
* StdDraw3D.ORBIT_MODE:
* Rotates and zooms around a central point.
*
* Mouse Click Left - Orbit
* Mouse Click Right - Pan
* Mouse Wheel / Alt-Click - Zoom
* --------------------------------------
* StdDraw3D.FPS_MODE:
* First-person-shooter style controls.
* Up is always the positive y-axis.
*
* W/Up - Forward
* S/Down - Backward
* A/Left - Left
* D/Right - Right
* Q/Page Up - Up
* E/Page Down - Down
* Mouse - Look
* --------------------------------------
* StdDraw3D.AIRPLANE_MODE:
* Similar to fps_mode, but can be oriented
* with up in any direction.
*
* W/Up - Forward
* S/Down - Backward
* A/Left - Left
* D/Right - Right
* Q/Page Up - Rotate CCW
* E/Page Down - Rotate CW
* Mouse - Look
* --------------------------------------
* StdDraw3D.LOOK_MODE:
* No movement, but uses mouse to look around.
*
* Mouse - Look
* --------------------------------------
* StdDraw3D.FIXED_MODE:
* No movement or looking.
* --------------------------------------
* @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 */ public void mouseClicked (MouseEvent e) { } /** * This method cannot be called directly. * @deprecated */ public void mouseEntered (MouseEvent e) { } /** * This method cannot be called directly. * @deprecated */ public void mouseExited (MouseEvent e) { } /** * This method cannot be called directly. * @deprecated */ 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 */ 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 */ 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 */ public void mouseMoved (MouseEvent e) { synchronized (mouseLock) { //System.out.println(e.getX() + " " + e.getY()); mouseMotionEvents(e, e.getX(), e.getY(), false); } } /** * This method cannot be called directly. * @deprecated */ 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, (double)(newY - mouseY) * zoom / 50); 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 */ 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 */ 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 */ 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) { if (!color.equals(bgColor)) { bgColor = color; rootGroup.removeChild(bgGroup); bgGroup.removeChild(background); background = createBackground(); background.setColor(new Color3f(bgColor)); 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(); } public static void clear (Color color) { setBackground(color); clear(); } /** * 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; int dt = 15; while (t > dt) { moveEvents(dt); Toolkit.getDefaultToolkit().sync(); try { Thread.currentThread().sleep(dt); } catch (InterruptedException e) { System.out.println("Error sleeping"); } t -= dt; } moveEvents(t); if (t == 0) return; try { Thread.currentThread().sleep(t); } catch (InterruptedException e) { System.out.println("Error sleeping"); } //while (!renderedOnce) { // try { Thread.currentThread().sleep(1); } // catch (InterruptedException e) { System.out.println("Error sleeping"); } // //renderedOnce = false; //showedOnce = true; } /** * 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); //StdOut.println("showed once = true"); } /* *************************************************************** * 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); javax.media.j3d.Text3D t = new javax.media.j3d.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((Geometry)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, boolean resize) { int params = 0; if (resize) 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 model (String filename, boolean resize) { return model(filename, false, resize); } public static Shape coloredModel (String filename) { return model(filename, true, true); } public static Shape coloredModel (String filename, boolean resize) { return model(filename, true, resize); } private static Shape model (String filename, boolean colored, boolean resize) { 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, resize); //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((int) Math.round(scaleX(x)), (int) 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((float) scaleX(x[0]), (float) scaleY(y[0])); for (int i = 0; i < N; i++) path.lineTo((float) scaleX(x[i]), (float) 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((float) scaleX(x[0]), (float) scaleY(y[0])); for (int i = 0; i < N; i++) path.lineTo((float) scaleX(x[i]), (float) 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", Font.PLAIN, 11)); g.setStroke(new BasicStroke( 1.0f, 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 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 class that represents anything that can be moved and rotated. */ 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); } private void setQuaternion (double x, double y, double z, double w) { setQuaternion(new Quat4d(x, y, z, w)); } private void setQuaternion (Quat4d quat) { Transform3D t = getTransform(); t.setRotation(quat); setTransform(t); } private 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 x, double y, double z) { move(new Vector3D(x, y, z)); } public void move (Vector3D move) { Transform3D t = getTransform(); Vector3f r = new Vector3f(); t.get(r); r.add(createVector3f(move)); 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 position) { camera.setPosition(position); } 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 x, double y, double z, double xAngle, double yAngle, double zAngle) { camera.setPosition(x, y, z); camera.setOrientation(xAngle, yAngle, zAngle); } public static void setCamera (Vector3D position, Vector3D angles) { camera.setPosition(position); camera.setOrientation(angles); } /** * Returns the Camera object. */ public static Camera camera () { return camera; } /** * The camera can be controlled with the static functions already listed in * this reference. However, much more advanced control of the camera can be * obtained by manipulating the Camera object. The Camera is basically * equivalent to a Shape with a couple of extra functions, so it can be moved * and rotated just like a Shape. This functionality works well with * point-of-view simulations and games. */ 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; } public void unpair () { pair = null; } 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)); } } /** * Everything three-dimensional you draw in your scene from spheres to * points to 3D text is actually a Shape object. When you call a drawing * function, it always returns a Shape object. If you keep a reference to * this object, you can manipulate the already drawn Shape instead of * clearing and redrawing it, which is a much more powerful and more * efficient method of animation. */ 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); } } /** * When you create a light in StdDraw3D, it returns a Light object. Light * objects can be manipulated just like Shapes, and are useful if you want * moving lights or lights that change color and brightness. */ 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 * @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 k. * * @param k Scalar to multiply by * @return (this.x * k, this.y * k, this.z * k) */ public Vector3D times (double k) { return times(k, k, k); } //-------------------------------------------------------------------------- /** * 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)" */ 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 StdDraw3D. */ 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) { // Sets the scale StdDraw3D.setScale(-1, 1); // 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(0.06); 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(new Font("Arial", Font.BOLD, 16)); StdDraw3D.Shape text = StdDraw3D.text3D(0, 0, 0, "StdDraw3D"); text.scale(3.5); text.move(-0.7, -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); } } } /************************************************************************* * Copyright 2002-2012, Robert Sedgewick and Kevin Wayne. * * This file is part of stdlib-package.jar, which accompanies the textbook * * Introduction to Programming in Java: An Interdisciplinary Approach * by R. Sedgewick and K. Wayne, Addison-Wesley, 2007. ISBN 0-321-49805-4. * * http://introcs.cs.princeton.edu * * * stdlib-package.jar is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * stdlib-package.jar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with stdlib-package.jar. If not, see http://www.gnu.org/licenses. *************************************************************************/




© 2015 - 2024 Weber Informatics LLC | Privacy Policy