/******************************************************************************
* 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 <a href = "http://introcs.cs.princeton.edu/java/stddraw3d">StdDraw3D tutorial</a>
* and the
* <a href = "http://introcs.cs.princeton.edu/java/stddraw3d/stddraw3d-manual.html">StdDraw3D reference manual</a>.
*/
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<Integer> keysDown = new TreeSet<Integer>();
private static LinkedList<Character> keysTyped = new LinkedList<Character>();
// For event synchronization.
private static Object mouseLock = new Object();
private static Object keyLock = new Object();
// Helper to see when initialization complete
private static boolean initialized = false;
private static boolean fullscreen = false;
private static boolean immersive = false;
// 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<Integer>(); }
/**
* This method cannot be called directly.
* @deprecated
*/
public void componentMoved(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
/**
* This method cannot be called directly.
* @deprecated
*/
public void componentShown(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
/**
* This method cannot be called directly.
* @deprecated
*/
public void componentResized(ComponentEvent e) { keysDown = new TreeSet<Integer>(); }
/**
* This method cannot be called directly.
* @deprecated
*/
public void windowGainedFocus(WindowEvent e) { keysDown = new TreeSet<Integer>(); }
/**
* This method cannot be called directly.
* @deprecated
*/
public void windowLostFocus(WindowEvent e) { keysDown = new TreeSet<Integer>(); }
/**
* Creates a blank BranchGroup with the proper capabilities.
*/
private static BranchGroup createBranchGroup () {
BranchGroup bg = new BranchGroup();
bg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
bg.setCapability(BranchGroup.ALLOW_DETACH);
bg.setPickable(false);
bg.setCollidable(false);
return bg;
}
/**
* Creates a blank TransformGroup with the proper capabilities.
*/
private static TransformGroup createTransformGroup () {
TransformGroup tg = new TransformGroup();
tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
tg.setPickable(false);
tg.setCollidable(false);
return tg;
}
/**
* Creates a blank Background with the proper capabilities.
*
* @return The created Background.
*/
private static Background createBackground () {
Background background = new Background();
background.setCapability(Background.ALLOW_COLOR_WRITE);
background.setCapability(Background.ALLOW_IMAGE_WRITE);
background.setCapability(Background.ALLOW_GEOMETRY_WRITE);
background.setApplicationBounds(INFINITE_BOUNDS);
return background;
}
/**
* Creates a texture from the given filename.
*
* @param imageURL Image file for creating texture.
* @return The created Texture.
* @throws RuntimeException if the file could not be read.
*/
private static Texture createTexture (String imageURL) {
TextureLoader loader;
try {
loader = new TextureLoader(imageURL, "RGBA", TextureLoader.Y_UP, new Container());
} catch (Exception e) {
throw new RuntimeException ("Could not read from the file '" + imageURL + "'");
}
Texture texture = loader.getTexture();
texture.setBoundaryModeS(Texture.WRAP);
texture.setBoundaryModeT(Texture.WRAP);
texture.setBoundaryColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.0f ) );
return texture;
}
private static Appearance createBlankAppearance () {
Appearance ap = new Appearance();
ap.setCapability(Appearance.ALLOW_MATERIAL_READ);
ap.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
ap.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
return ap;
}
/**
* Creates an Appearance.
*
* @param imageURL Wraps a texture around from this image file.
* @param fill If true, fills in faces. If false, outlines.
* @return The created appearance.
*/
private static Appearance createAppearance (String imageURL, boolean fill) {
Appearance ap = createBlankAppearance();
PolygonAttributes pa = new PolygonAttributes();
if (!fill) pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
pa.setCullFace(PolygonAttributes.CULL_NONE);
ap.setPolygonAttributes(pa);
if (imageURL != null) {
Texture texture = createTexture(imageURL);
TextureAttributes texAttr = new TextureAttributes();
texAttr.setTextureMode(TextureAttributes.REPLACE);
ap.setTexture(texture);
ap.setTextureAttributes(texAttr);
}
Color3f col = new Color3f(penColor);
Color3f black = new Color3f(0, 0, 0);
Color3f specular = new Color3f(GRAY);
// Material properties
Material material = new Material(col, black, col, specular, 64);
material.setCapability(Material.ALLOW_COMPONENT_READ);
material.setCapability(Material.ALLOW_COMPONENT_WRITE);
material.setLightingEnable(true);
ap.setMaterial(material);
// Transparecy properties
float alpha = ((float)penColor.getAlpha()) / 255;
if (alpha < 1.0) {
TransparencyAttributes t = new TransparencyAttributes();
t.setTransparencyMode(TransparencyAttributes.BLENDED);
t.setTransparency(1 - alpha);
ap.setTransparencyAttributes(t);
}
LineAttributes la = new LineAttributes();
la.setLineWidth(penRadius);
la.setLineAntialiasingEnable(view.getSceneAntialiasingEnable());
PointAttributes poa = new PointAttributes();
poa.setPointAntialiasingEnable(view.getSceneAntialiasingEnable());
ColoringAttributes ca = new ColoringAttributes();
ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
ca.setColor(col);
ap.setLineAttributes(la);
ap.setPointAttributes(poa);
ap.setColoringAttributes(ca);
return ap;
}
// FIX THIS check that adding specular didn't mess up custom shapes (lines, triangles, points)
private static Appearance createCustomAppearance (boolean fill) {
Appearance ap = createBlankAppearance();
PolygonAttributes pa = new PolygonAttributes();
if (!fill) pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
pa.setCullFace(PolygonAttributes.CULL_NONE);
LineAttributes la = new LineAttributes();
la.setLineWidth(penRadius);
la.setLineAntialiasingEnable(view.getSceneAntialiasingEnable());
PointAttributes poa = new PointAttributes();
poa.setPointAntialiasingEnable(view.getSceneAntialiasingEnable());
ap.setPolygonAttributes(pa);
ap.setLineAttributes(la);
ap.setPointAttributes(poa);
Color3f col = new Color3f(penColor);
Color3f black = new Color3f(0, 0, 0);
Color3f specular = new Color3f(GRAY);
// Material properties
Material material = new Material(col, black, col, specular, 64);
material.setCapability(Material.ALLOW_COMPONENT_READ);
material.setCapability(Material.ALLOW_COMPONENT_WRITE);
material.setLightingEnable(true);
ap.setMaterial(material);
return ap;
}
private static Shape3D createShape3D (Geometry geom) {
Shape3D shape = new Shape3D(geom);
shape.setPickable(false);
shape.setCollidable(false);
shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
shape.setCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_READ);
shape.setCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE);
return shape;
}
/**
* Creates a blank BufferedImage.
*
* @return The created BufferedImage.
*/
private static BufferedImage createBufferedImage () {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
/** Converts a Vector3D to a Vector3d. */
private static Vector3d createVector3d (Vector3D v) {
return new Vector3d(v.x, v.y, v.z);
}
/** Converts three double scalars to a Vector3f. **/
private static Vector3f createVector3f (double x, double y, double z) {
return new Vector3f((float)x, (float)y, (float)z);
}
/** Converts a Vector3D to a Vector3f. */
private static Vector3f createVector3f (Vector3D v) {
return createVector3f(v.x, v.y, v.z);
}
/** Converts a Vector3D to a Point3f. */
private static Point3f createPoint3f (Vector3D v) {
return createPoint3f(v.x, v.y, v.z);
}
/** Converts three double scalars to a Point3f. */
private static Point3f createPoint3f (double x, double y, double z) {
return new Point3f((float)x, (float)y, (float)z);
}
/**
* Creates a scanner from the filename or website name.
*/
private static Scanner createScanner (String s) {
Scanner scanner;
String charsetName = "ISO-8859-1";
java.util.Locale usLocale = new java.util.Locale("en", "US");
try {
// first try to read file from local file system
File file = new File(s);
if (file.exists()) {
scanner = new Scanner(file, charsetName);
scanner.useLocale(usLocale);
return scanner;
}
// next try for website
URL url = new URL(s);
URLConnection site = url.openConnection();
InputStream is = site.getInputStream();
scanner = new Scanner(new BufferedInputStream(is), charsetName);
scanner.useLocale(usLocale);
return scanner;
}
catch (IOException ioe) {
System.err.println("Could not open " + s + ".");
return null;
}
}
/* ***************************************************************
* Scaling and Screen Methods *
*****************************************************************/
/**
* Sets the window size to w-by-h pixels.
*
* @param w The width as a number of pixels.
* @param h The height as a number of pixels.
* @throws a RuntimeException if the width or height is 0 or negative.
*/
public static void setCanvasSize (int w, int h) {
setCanvasSize(w, h, false);
}
private static void setCanvasSize (int w, int h, boolean fs) {
fullscreen = fs;
if (w < 1 || h < 1) throw new RuntimeException("Dimensions must be positive integers!");
width = w;
height = h;
aspectRatio = (double)width / (double)height;
initialize();
}
/**
* Sets the default scale for all three dimensions.
*/
public static void setScale () { setScale(DEFAULT_MIN, DEFAULT_MAX); }
/**
* Sets the scale for all three dimensions.
*
* @param minimum The minimum value of each scale.
* @param maximum The maximum value of each scale.
*/
public static void setScale (double minimum, double maximum) {
min = minimum;
max = maximum;
zoom = (max - min) / 2;
double center = min + zoom;
//double nominalDist = camera.getPosition().z;
camera.setPosition(center, center, zoom * (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. <br>
* -------------------------------------- <br>
* StdDraw3D.ORBIT_MODE: <br>
* Rotates and zooms around a central point. <br>
* <br>
* Mouse Click Left - Orbit <br>
* Mouse Click Right - Pan <br>
* Mouse Wheel / Alt-Click - Zoom <br>
* -------------------------------------- <br>
* StdDraw3D.FPS_MODE: <br>
* First-person-shooter style controls. <br>
* Up is always the positive y-axis. <br>
* <br>
* W/Up - Forward <br>
* S/Down - Backward <br>
* A/Left - Left <br>
* D/Right - Right <br>
* Q/Page Up - Up <br>
* E/Page Down - Down <br>
* Mouse - Look <br>
* -------------------------------------- <br>
* StdDraw3D.AIRPLANE_MODE: <br>
* Similar to fps_mode, but can be oriented <br>
* with up in any direction. <br>
* <br>
* W/Up - Forward <br>
* S/Down - Backward <br>
* A/Left - Left <br>
* D/Right - Right <br>
* Q/Page Up - Rotate CCW <br>
* E/Page Down - Rotate CW <br>
* Mouse - Look <br>
* -------------------------------------- <br>
* StdDraw3D.LOOK_MODE: <br>
* No movement, but uses mouse to look around. <br>
* <br>
* Mouse - Look <br>
* -------------------------------------- <br>
* StdDraw3D.FIXED_MODE: <br>
* No movement or looking. <br>
* -------------------------------------- <br>
* @param mode The camera mode.
*/
public static void setCameraMode (int mode) {
cameraMode = mode;
if (cameraMode == ORBIT_MODE) {
orbit.setRotateEnable(true);
if (view.getProjectionPolicy() != View.PARALLEL_PROJECTION)
orbit.setZoomEnable(true);
orbit.setTranslateEnable(true);
orbit.setRotationCenter(orbitCenter);
orbitModeButton.setSelected(true);
} else {
orbit.setRotateEnable(false);
orbit.setZoomEnable(false);
orbit.setTranslateEnable(false);
}
if (cameraMode == FPS_MODE) {
fpsModeButton.setSelected(true);
camera.rotateFPS(0, 0, 0);
}
if (cameraMode == AIRPLANE_MODE) {
airplaneModeButton.setSelected(true);
}
if (cameraMode == LOOK_MODE) {
//System.out.println("Camera in look mode.");
lookModeButton.setSelected(true);
}
if (cameraMode == FIXED_MODE) {
//System.out.println("Camera in fixed mode.");
fixedModeButton.setSelected(true);
}
if (cameraMode == IMMERSIVE_MODE) {
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new java.awt.Point(0, 0), "blank cursor");
frame.getContentPane().setCursor(blankCursor);
//System.out.println("Camera in fps mode.");
} else {
frame.getContentPane().setCursor(Cursor.getDefaultCursor());
}
}
/**
* Sets the default camera mode.
*/
public static void setCameraMode() { setCameraMode(DEFAULT_CAMERA_MODE); }
/**
* Sets the center of rotation for the camera mode ORBIT_MODE.
*/
public static void setOrbitCenter(double x, double y, double z) {
setOrbitCenter(new Point3d(x, y, z));
}
/**
* Sets the center of rotation for the camera mode ORBIT_MODE.
*/
public static void setOrbitCenter(Vector3D v) {
setOrbitCenter(new Point3d(v.x, v.y, v.z));
}
private static void setOrbitCenter(Point3d center) {
orbitCenter = center;
orbit.setRotationCenter(orbitCenter);
}
public static Vector3D getOrbitCenter() {
return new Vector3D(orbitCenter);
}
/**
* Sets the projection mode to perspective projection with a
* default field of view. In this mode, closer objects appear
* larger, as in real life.
*/
public static void setPerspectiveProjection () {
setPerspectiveProjection(DEFAULT_FOV);
}
/**
* Sets the projection mode to perspective projection with the
* given field of view. In this mode, closer objects appear
* larger, as in real life. A larger field of view means that
* there is more perspective distortion. Reasonable values are
* between 0.5 and 3.0.
*
* @param fov The field of view to use. Try [0.5-3.0].
*/
public static void setPerspectiveProjection (double fov) {
view.setProjectionPolicy(View.PERSPECTIVE_PROJECTION);
view.setWindowEyepointPolicy(View.RELATIVE_TO_FIELD_OF_VIEW);
view.setFieldOfView(fov);
setScreenScale(1);
orbit.setZoomEnable(true);
perspectiveButton.setSelected(true);
if ((Double)fovSpinner.getValue() != fov)
fovSpinner.setValue(fov);
//if (view.getProjectionPolicy() == View.PERSPECTIVE_PROJECTION) return;
}
/**
* Sets the projection mode to orthographic projection.
* In this mode, parallel lines remain parallel after
* projection, and there is no perspective. It is as
* looking from infinitely far away with a telescope.
* AutoCAD programs use this projection mode.
*/
public static void setParallelProjection () {
if (view.getProjectionPolicy() == View.PARALLEL_PROJECTION) return;
view.setProjectionPolicy(View.PARALLEL_PROJECTION);
orbit.setZoomEnable(false);
parallelButton.setSelected(true);
setScreenScale(0.3 / zoom);
}
private static void setScreenScale (double scale) {
double ratio = scale / view.getScreenScale();
view.setScreenScale(scale);
view.setFrontClipDistance(view.getFrontClipDistance() * ratio);
view.setBackClipDistance(view.getBackClipDistance() * ratio);
}
/* ***************************************************************
* Mouse Listener Methods *
*****************************************************************/
/** Is any mouse button pressed? */
public static boolean mousePressed () {
synchronized (mouseLock) {
return (mouse1Pressed() || mouse2Pressed() || mouse3Pressed());
}
}
/**
* Is the mouse1 button pressed down?
*/
public static boolean mouse1Pressed () {
synchronized (mouseLock) {
return mouse1;
}
}
/**
* Is the mouse2 button pressed down?
*/
public static boolean mouse2Pressed () {
synchronized (mouseLock) {
return mouse2;
}
}
/**
* Is the mouse3 button pressed down?
*/
public static boolean mouse3Pressed () {
synchronized (mouseLock) {
return mouse3;
}
}
/**
* Where is the mouse?
*
* @return The value of the X-coordinate of the mouse.
*/
public static double mouseX () {
synchronized (mouseLock) {
return unscaleX(mouseX);
}
}
/**
* Where is the mouse?
* @return The value of the Y-coordinate of the mouse.
*/
public static double mouseY () {
synchronized (mouseLock) {
return unscaleY(mouseY);
}
}
/**
* This method cannot be called directly.
* @deprecated
*/
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 <hmartiro@princeton.edu>
* @since 2011.0310
* @version 1.0
****************************************************************************/
public static class Vector3D {
/** X-coordinate - immutable but directly accessible. */
public final double x;
/** Y-coordinate - immutable but directly accessible. */
public final double y;
/** Z-coordinate - immutable but directly accessible. */
public final double z;
//--------------------------------------------------------------------------
/**
* Initializes to zero vector.
*/
public Vector3D () {
this.x = 0;
this.y = 0;
this.z = 0;
}
//--------------------------------------------------------------------------
/**
* Initializes to the given coordinates.
*
* @param x X-coordinate
* @param y Y-coordinate
* @param z Z-coordinate
*/
public Vector3D (double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Initializes to the given coordinates.
*
* @param c Array length 3 of coordinates (x, y, z,).
*/
//--------------------------------------------------------------------------
public Vector3D (double[] c) {
if (c.length != 3)
throw new RuntimeException("Incorrect number of dimensions!");
this.x = c[0];
this.y = c[1];
this.z = c[2];
}
//--------------------------------------------------------------------------
private Vector3D (Vector3d v) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
}
//--------------------------------------------------------------------------
private Vector3D (Vector3f v) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
}
//--------------------------------------------------------------------------
private Vector3D (Point3d p) {
this.x = p.x;
this.y = p.y;
this.z = p.z;
}
//--------------------------------------------------------------------------
private Vector3D (Point3f p) {
this.x = p.x;
this.y = p.y;
this.z = p.z;
}
//--------------------------------------------------------------------------
/*
public Vector3D (double min, double max) {
this.x = min + Math.random() * (max - min);
this.y = min + Math.random() * (max - min);
this.z = min + Math.random() * (max - min);
}
*/
//--------------------------------------------------------------------------
/**
* Returns the dot product of this and that.
*
* @param that Vector to dot with
* @return This dot that
*/
public double dot (Vector3D that) {
return (this.x * that.x + this.y * that.y + this.z * that.z);
}
//--------------------------------------------------------------------------
/**
* Returns the magnitude of this vector.
*
* @return Magnitude of vector
*/
public double mag () {
return Math.sqrt(this.dot(this));
}
//--------------------------------------------------------------------------
/**
* Returns the smallest angle between this and that vector, in DEGREES.
*
* @return Angle between the vectors, in DEGREES
*/
public double angle (Vector3D that) {
return Math.toDegrees(Math.acos(this.dot(that)/(this.mag() * that.mag())));
}
//--------------------------------------------------------------------------
/**
* Returns the Euclidian distance between this and that.
*
* @param that Vector to compute distance between
* @return Distance between this and that
*/
public double distanceTo (Vector3D that) {
return this.minus(that).mag();
}
//--------------------------------------------------------------------------
/**
* Returns the sum of this and that vector.
*
* @param that Vector to compute sum with
* @return This plus that
*/
public Vector3D plus (Vector3D that) {
double cx = this.x + that.x;
double cy = this.y + that.y;
double cz = this.z + that.z;
Vector3D c = new Vector3D(cx, cy, cz);
return c;
}
public Vector3D plus (double x, double y, double z) {
double cx = this.x + x;
double cy = this.y + y;
double cz = this.z + z;
return new Vector3D(cx, cy, cz);
}
//--------------------------------------------------------------------------
/**
* Returns the difference of this and that vector.
*
* @param that Vector to compute difference with
* @return This minus that
*/
public Vector3D minus (Vector3D that) {
double cx = this.x - that.x;
double cy = this.y - that.y;
double cz = this.z - that.z;
Vector3D c = new Vector3D(cx, cy, cz);
return c;
}
public Vector3D minus (double x, double y, double z) {
double cx = this.x - x;
double cy = this.y - y;
double cz = this.z - z;
return new Vector3D(cx, cy, cz);
}
//--------------------------------------------------------------------------
/**
* Returns the product of this vector and the scalar 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);
}
}
}