3D Graphics Java

/*
 * @(#)TextureByReference.java 1.14 02/10/21 14:36:22
 * 
 * Copyright (c) 1996-2002 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: -
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. - Redistribution in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.Enumeration;
import javax.media.j3d.Alpha;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Material;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TriangleArray;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.vecmath.Color3f;
import javax.vecmath.Point2f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.Vector3f;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;
public class TextureByReference extends Applet implements ItemListener,
    ActionListener, ChangeListener {
  // need reference to animation behavior
  private AnimateTexturesBehavior animate;
  // need reference to tetrahedron
  private Tetrahedron tetra;
  // the gui buttons
  private JCheckBox flipB;
  private JRadioButton texByRef;
  private JRadioButton texByCopy;
  private JRadioButton geomByRef;
  private JRadioButton geomByCopy;
  private JRadioButton img4ByteABGR;
  private JRadioButton img3ByteBGR;
  private JRadioButton imgIntARGB;
  private JRadioButton imgCustomRGBA;
  private JRadioButton imgCustomRGB;
  private JRadioButton yUp;
  private JRadioButton yDown;
  private JButton animationB;
  private JSlider frameDelay;
  private SimpleUniverse universe = null;
  // image files used for the Texture animation for the applet,
  // or if no parameters are passed in for the application
  public static final String[] defaultFiles = { "animation1.gif",
      "animation2.gif", "animation3.gif",
      "animation4.gif", "animation5.gif",
      "animation6.gif", "animation7.gif",
      "animation8.gif", "animation9.gif",
      "animation10.gif" };
  private java.net.URL[] urls = null;
  public TextureByReference() {
  }
  public TextureByReference(java.net.URL[] fnamesP) {
    urls = fnamesP;
  }
  public void init() {
    if (urls == null) {
      urls = new java.net.URL[defaultFiles.length];
      for (int i = 0; i < defaultFiles.length; i++) {
        try {
          urls[i] = new java.net.URL(getCodeBase().toString()
              + defaultFiles[i]);
        } catch (java.net.MalformedURLException ex) {
          System.out.println(ex.getMessage());
          System.exit(1);
        }
      }
    }
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();
    Canvas3D canvas = new Canvas3D(config);
    add("Center", canvas);
    // create a simple scene graph and attach it to a simple universe
    BranchGroup scene = createSceneGraph();
    universe = new SimpleUniverse(canvas);
    universe.getViewingPlatform().setNominalViewingTransform();
    universe.addBranchGraph(scene);
    // create the gui
    JPanel gui = buildGui();
    this.add("South", gui);
  }
  public void destroy() {
    universe.cleanup();
  }
  public JPanel buildGui() {
    flipB = new JCheckBox("flip image", true);
    flipB.addItemListener(this);
    javax.swing.Box flipBox = new javax.swing.Box(BoxLayout.Y_AXIS);
    flipBox.add(flipB);
    Component strut1 = flipBox
        .createVerticalStrut(flipB.getPreferredSize().height);
    Component strut2 = flipBox
        .createVerticalStrut(flipB.getPreferredSize().height);
    Component strut3 = flipBox
        .createVerticalStrut(flipB.getPreferredSize().height);
    Component strut4 = flipBox
        .createVerticalStrut(flipB.getPreferredSize().height);
    Component strut5 = flipBox
        .createVerticalStrut(flipB.getPreferredSize().height);
    flipBox.add(strut1);
    flipBox.add(strut2);
    flipBox.add(strut3);
    flipBox.add(strut4);
    flipBox.add(strut5);
    yUp = new JRadioButton("y up");
    yUp.addActionListener(this);
    yUp.setSelected(true);
    yDown = new JRadioButton("y down");
    yDown.addActionListener(this);
    ButtonGroup yGroup = new ButtonGroup();
    yGroup.add(yUp);
    yGroup.add(yDown);
    JLabel yLabel = new JLabel("Image Orientation:");
    javax.swing.Box yBox = new javax.swing.Box(BoxLayout.Y_AXIS);
    yBox.add(yLabel);
    yBox.add(yUp);
    yBox.add(yDown);
    strut1 = yBox.createVerticalStrut(yUp.getPreferredSize().height);
    strut2 = yBox.createVerticalStrut(yUp.getPreferredSize().height);
    strut3 = yBox.createVerticalStrut(yUp.getPreferredSize().height);
    yBox.add(strut1);
    yBox.add(strut2);
    yBox.add(strut3);
    texByRef = new JRadioButton("by reference");
    texByRef.addActionListener(this);
    texByRef.setSelected(true);
    texByCopy = new JRadioButton("by copy");
    texByCopy.addActionListener(this);
    ButtonGroup texGroup = new ButtonGroup();
    texGroup.add(texByRef);
    texGroup.add(texByCopy);
    JLabel texLabel = new JLabel("Texture:*");
    javax.swing.Box texBox = new javax.swing.Box(BoxLayout.Y_AXIS);
    texBox.add(texLabel);
    texBox.add(texByRef);
    texBox.add(texByCopy);
    strut1 = texBox.createVerticalStrut(texByRef.getPreferredSize().height);
    strut2 = texBox.createVerticalStrut(texByRef.getPreferredSize().height);
    strut3 = texBox.createVerticalStrut(texByRef.getPreferredSize().height);
    texBox.add(strut1);
    texBox.add(strut2);
    texBox.add(strut3);
    geomByRef = new JRadioButton("by reference");
    geomByRef.addActionListener(this);
    geomByRef.setSelected(true);
    geomByCopy = new JRadioButton("by copy");
    geomByCopy.addActionListener(this);
    ButtonGroup geomGroup = new ButtonGroup();
    geomGroup.add(geomByRef);
    geomGroup.add(geomByCopy);
    JLabel geomLabel = new JLabel("Geometry:");
    javax.swing.Box geomBox = new javax.swing.Box(BoxLayout.Y_AXIS);
    geomBox.add(geomLabel);
    geomBox.add(geomByRef);
    geomBox.add(geomByCopy);
    strut1 = geomBox
        .createVerticalStrut(geomByRef.getPreferredSize().height);
    strut2 = geomBox
        .createVerticalStrut(geomByRef.getPreferredSize().height);
    strut3 = geomBox
        .createVerticalStrut(geomByRef.getPreferredSize().height);
    geomBox.add(strut1);
    geomBox.add(strut2);
    geomBox.add(strut3);
    img4ByteABGR = new JRadioButton("TYPE_4BYTE_ABGR");
    img4ByteABGR.addActionListener(this);
    img4ByteABGR.setSelected(true);
    img3ByteBGR = new JRadioButton("TYPE_3BYTE_BGR");
    img3ByteBGR.addActionListener(this);
    imgIntARGB = new JRadioButton("TYPE_INT_ARGB");
    imgIntARGB.addActionListener(this);
    imgCustomRGBA = new JRadioButton("TYPE_CUSTOM RGBA");
    imgCustomRGBA.addActionListener(this);
    imgCustomRGB = new JRadioButton("TYPE_CUSTOM RGB");
    imgCustomRGB.addActionListener(this);
    ButtonGroup imgGroup = new ButtonGroup();
    imgGroup.add(img4ByteABGR);
    imgGroup.add(img3ByteBGR);
    imgGroup.add(imgIntARGB);
    imgGroup.add(imgCustomRGBA);
    imgGroup.add(imgCustomRGB);
    JLabel imgLabel = new JLabel("Image Type:*");
    javax.swing.Box imgBox = new javax.swing.Box(BoxLayout.Y_AXIS);
    imgBox.add(imgLabel);
    imgBox.add(img4ByteABGR);
    imgBox.add(img3ByteBGR);
    imgBox.add(imgIntARGB);
    imgBox.add(imgCustomRGBA);
    imgBox.add(imgCustomRGB);
    javax.swing.Box topBox = new javax.swing.Box(BoxLayout.X_AXIS);
    topBox.add(flipBox);
    topBox.add(texBox);
    topBox.add(geomBox);
    topBox.add(yBox);
    Component strut = topBox.createRigidArea(new Dimension(10, 10));
    topBox.add(strut);
    topBox.add(imgBox);
    frameDelay = new JSlider(0, 50, 0);
    frameDelay.addChangeListener(this);
    frameDelay.setSnapToTicks(true);
    frameDelay.setPaintTicks(true);
    frameDelay.setPaintLabels(true);
    frameDelay.setMajorTickSpacing(10);
    frameDelay.setMinorTickSpacing(1);
    frameDelay.setValue(20);
    JLabel delayL = new JLabel("frame delay");
    javax.swing.Box delayBox = new javax.swing.Box(BoxLayout.X_AXIS);
    delayBox.add(delayL);
    delayBox.add(frameDelay);
    animationB = new JButton(" stop animation ");
    animationB.addActionListener(this);
    JLabel texInfo1 = new JLabel(
        "*To use ImageComponent by reference feature, use TYPE_4BYTE_ABGR on Solaris");
    JLabel texInfo2 = new JLabel("and TYPE_3BYTE_BGR on Windows");
    JPanel buttonP = new JPanel();
    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints c = new GridBagConstraints();
    buttonP.setLayout(gridbag);
    c.anchor = GridBagConstraints.CENTER;
    c.gridwidth = GridBagConstraints.REMAINDER;
    gridbag.setConstraints(topBox, c);
    buttonP.add(topBox);
    gridbag.setConstraints(delayBox, c);
    buttonP.add(delayBox);
    gridbag.setConstraints(animationB, c);
    buttonP.add(animationB);
    gridbag.setConstraints(texInfo1, c);
    buttonP.add(texInfo1);
    gridbag.setConstraints(texInfo2, c);
    buttonP.add(texInfo2);
    return buttonP;
  }
  public BranchGroup createSceneGraph() {
    // create the root of the branch group
    BranchGroup objRoot = new BranchGroup();
    // create the transform group node and initialize it
    // enable the TRANSFORM_WRITE capability so that it can be modified
    // at runtime. Add it to the root of the subgraph
    Transform3D rotate = new Transform3D();
    TransformGroup objTrans = new TransformGroup(rotate);
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objRoot.addChild(objTrans);
    // bounds
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);
    // set up some light
    Color3f lColor1 = new Color3f(0.7f, 0.7f, 0.7f);
    Vector3f lDir1 = new Vector3f(-1.0f, -0.5f, -1.0f);
    Color3f alColor = new Color3f(0.2f, 0.2f, 0.2f);
    AmbientLight aLgt = new AmbientLight(alColor);
    aLgt.setInfluencingBounds(bounds);
    DirectionalLight lgt1 = new DirectionalLight(lColor1, lDir1);
    lgt1.setInfluencingBounds(bounds);
    objRoot.addChild(aLgt);
    objRoot.addChild(lgt1);
    Appearance appearance = new Appearance();
    // enable the TEXTURE_WRITE so we can modify it at runtime
    appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
    // load the first texture
    TextureLoader loader = new TextureLoader(urls[0],
        TextureLoader.BY_REFERENCE | TextureLoader.Y_UP, this);
    // get the texture from the loader
    Texture2D tex = (Texture2D) loader.getTexture();
    // get the BufferedImage to convert to TYPE_4BYTE_ABGR and flip
    // get the ImageComponent because we need it anyway
    ImageComponent2D imageComp = (ImageComponent2D) tex.getImage(0);
    BufferedImage bImage = imageComp.getImage();
    // convert the image
    bImage = ImageOps.convertImage(bImage, BufferedImage.TYPE_4BYTE_ABGR);
    // flip the image
    ImageOps.flipImage(bImage);
    imageComp.set(bImage);
    tex.setCapability(Texture.ALLOW_IMAGE_WRITE);
    tex.setBoundaryModeS(Texture.CLAMP);
    tex.setBoundaryModeT(Texture.CLAMP);
    tex.setBoundaryColor(1.0f, 1.0f, 1.0f, 1.0f);
    // set the image of the texture
    tex.setImage(0, imageComp);
    // set the texture on the appearance
    appearance.setTexture(tex);
    // set texture attributes
    TextureAttributes texAttr = new TextureAttributes();
    texAttr.setTextureMode(TextureAttributes.MODULATE);
    appearance.setTextureAttributes(texAttr);
    // set material properties
    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
    appearance.setMaterial(new Material(white, black, white, black, 1.0f));
    // create a scale transform
    Transform3D scale = new Transform3D();
    scale.set(.6);
    TransformGroup objScale = new TransformGroup(scale);
    objTrans.addChild(objScale);
    tetra = new Tetrahedron(true);
    tetra.setAppearance(appearance);
    objScale.addChild(tetra);
    // create the behavior
    animate = new AnimateTexturesBehavior(tex, urls, appearance, this);
    animate.setSchedulingBounds(bounds);
    objTrans.addChild(animate);
    // add a rotation behavior so we can see all sides of the tetrahedron
    Transform3D yAxis = new Transform3D();
    Alpha rotorAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0, 4000,
        0, 0, 0, 0, 0);
    RotationInterpolator rotator = new RotationInterpolator(rotorAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    rotator.setSchedulingBounds(bounds);
    objTrans.addChild(rotator);
    // have java3d perform optimizations on this scene graph
    objRoot.compile();
    return objRoot;
  }
  // callback for the animation button and delay text field
  public void actionPerformed(ActionEvent e) {
    Object o = e.getSource();
    // for the animation button
    if (o == animationB) {
      if (animate.getEnable()) {
        animate.setEnable(false);
        animationB.setText("start animation");
      } else {
        animate.setEnable(true);
        animationB.setText(" stop animation ");
      }
    }
    // for the texByRef button
    else if (o == texByRef && texByRef.isSelected()) {
      animate.setByReference(true);
    }
    // texByCopy button
    else if (o == texByCopy && texByCopy.isSelected()) {
      animate.setByReference(false);
    }
    // yUp button
    else if (o == yUp && yUp.isSelected()) {
      animate.setYUp(true);
    }
    // ydown button
    else if (o == yDown && yDown.isSelected()) {
      animate.setYUp(false);
    }
    //geomByRef button
    else if (o == geomByRef) {
      tetra.setByReference(true);
    }
    // geomByCopy button
    else if (o == geomByCopy) {
      tetra.setByReference(false);
    }
    // TYPE_INT_ARGB
    else if (o == imgIntARGB) {
      animate.setImageType(BufferedImage.TYPE_INT_ARGB);
    }
    // TYPE_4BYTE_ABGR
    else if (o == img4ByteABGR) {
      animate.setImageType(BufferedImage.TYPE_4BYTE_ABGR);
    }
    // TYPE_3BYTE_BGR
    else if (o == img3ByteBGR) {
      animate.setImageType(BufferedImage.TYPE_3BYTE_BGR);
    }
    // TYPE_CUSTOM RGBA
    else if (o == imgCustomRGBA) {
      animate.setImageTypeCustomRGBA();
    }
    // TYPE_CUSTOM RGB
    else if (o == imgCustomRGB) {
      animate.setImageTypeCustomRGB();
    }
  }
  // callback for the checkboxes
  public void itemStateChanged(ItemEvent e) {
    Object o = e.getSource();
    // for the flip checkbox
    if (o == flipB) {
      if (e.getStateChange() == ItemEvent.DESELECTED) {
        animate.setFlipImages(false);
      } else
        animate.setFlipImages(true);
    }
  }
  // callback for the slider
  public void stateChanged(ChangeEvent e) {
    Object o = e.getSource();
    // for the frame delay
    if (o == frameDelay) {
      animate.setFrameDelay(frameDelay.getValue());
    }
  }
  // allows TextureByReference to be run as an application as well as an
  // applet
  public static void main(String[] args) {
    java.net.URL fnames[] = null;
    if (args.length > 1) {
      fnames = new java.net.URL[args.length];
      for (int i = 0; i < args.length; i++) {
        try {
          fnames[i] = new java.net.URL("file:" + args[i]);
        } catch (java.net.MalformedURLException ex) {
          System.out.println(ex.getMessage());
        }
      }
    } else {
      fnames = new java.net.URL[TextureByReference.defaultFiles.length];
      for (int i = 0; i < TextureByReference.defaultFiles.length; i++) {
        try {
          fnames[i] = new java.net.URL("file:"
              + TextureByReference.defaultFiles[i]);
        } catch (java.net.MalformedURLException ex) {
          System.out.println(ex.getMessage());
          System.exit(1);
        }
      }
    }
    new MainFrame((new TextureByReference(fnames)), 650, 750);
  }
}
class AnimateTexturesBehavior extends Behavior {
  // what image are we on
  private int current;
  private int max;
  // the images
  private ImageComponent2D[] images;
  // the target
  private Texture2D texture;
  private Appearance appearance;
  // the wakeup criterion
  private WakeupCriterion wakeupC;
  // are the images flipped?
  private boolean flip;
  // need the current type because by copy changes all images
  // to TYPE_INT_ARGB
  private int currentType;
  // for custom types
  public static final int TYPE_CUSTOM_RGBA = 0x01;
  public static final int TYPE_CUSTOM_RGB = 0x02;
  private int customType;
  // create a new AnimateTextureBehavior
  // initialize the images
  public AnimateTexturesBehavior(Texture2D texP, java.net.URL[] fnames,
      Appearance appP, TextureByReference applet) {
    int size = fnames.length;
    images = new ImageComponent2D[size];
    BufferedImage bImage;
    TextureLoader loader;
    for (int i = 0; i < size; i++) {
      loader = new TextureLoader(fnames[i], TextureLoader.BY_REFERENCE
          | TextureLoader.Y_UP, applet);
      images[i] = loader.getImage();
      bImage = images[i].getImage();
      // convert the image to TYPE_4BYTE_ABGR
      currentType = BufferedImage.TYPE_4BYTE_ABGR;
      bImage = ImageOps.convertImage(bImage, currentType);
      // flip the image
      flip = true;
      ImageOps.flipImage(bImage);
      // set the image on the ImageComponent to the new one
      images[i].set(bImage);
      images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
      images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
    }
    texture = texP;
    current = 0;
    max = size;
    wakeupC = new WakeupOnElapsedFrames(20);
    appearance = appP;
  }
  // initialize to the first image
  public void initialize() {
    texture.setImage(0, images[current]);
    if (current < max - 1)
      current++;
    else
      current = 0;
    wakeupOn(wakeupC);
  }
  // procesStimulus changes the ImageComponent of the texture
  public void processStimulus(Enumeration criteria) {
    //    ImageOps.printType(images[current].getImage());
    texture.setImage(0, images[current]);
    appearance.setTexture(texture);
    if (current < max - 1)
      current++;
    else
      current = 0;
    wakeupOn(wakeupC);
  }
  // flip the image -- useful depending on yUp
  public void setFlipImages(boolean b) {
    // double check that flipping is necessary
    if (b != flip) {
      BufferedImage bImage;
      // these are the same for all images so get info once
      int format = images[0].getFormat();
      boolean byRef = images[0].isByReference();
      boolean yUp = images[0].isYUp();
      // flip all the images
      // have to new ImageComponents because can't set the image at
      // runtime
      for (int i = 0; i < images.length; i++) {
        bImage = images[i].getImage();
        ImageOps.flipImage(bImage);
        // if we are byRef and the bImage type does not match
        // currentType
        // we need to convert it. If we are not byRef we will
        // save converting until it is changed to byRef
        if (byRef && bImage.getType() != currentType) {
          if (currentType != BufferedImage.TYPE_CUSTOM) {
            bImage = ImageOps.convertImage(bImage, currentType);
          } else if (customType == this.TYPE_CUSTOM_RGBA) {
            bImage = ImageOps.convertToCustomRGBA(bImage);
          } else {
            bImage = ImageOps.convertToCustomRGB(bImage);
          }
        }
        images[i] = new ImageComponent2D(format, bImage, byRef, yUp);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
      // set flip to new value
      flip = b;
    }
  }
  // create new ImageComponents with yUp set to the parameter. yUp on
  // an ImageComponent cannot be changed at runtim
  public void setYUp(boolean b) {
    // double check that changing yUp is necessary
    if (b != images[0].isYUp()) {
      // these are the same for all images so get info once
      int format = images[0].getFormat();
      boolean byRef = images[0].isByReference();
      // reset yUp on all the images -- have to new ImageComponents
      // because
      // cannot change the value at runtime
      for (int i = 0; i < images.length; i++) {
        // if we are byRef and the bImage type does not match
        // currentType
        // we need to convert it. If we are not byRef we will
        // save converting until it is changed to byRef
        BufferedImage bImage = images[i].getImage();
        if (byRef && bImage.getType() != currentType) {
          //    bImage = ImageOps.convertImage(bImage, currentType);
          if (currentType != BufferedImage.TYPE_CUSTOM) {
            bImage = ImageOps.convertImage(bImage, currentType);
          } else if (customType == this.TYPE_CUSTOM_RGBA) {
            bImage = ImageOps.convertToCustomRGBA(bImage);
          } else {
            bImage = ImageOps.convertToCustomRGB(bImage);
          }
        }
        images[i] = new ImageComponent2D(format, bImage, byRef, b);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
    }
  }
  // create new ImageComponents with ByReference set by parameter.
  // by reference cannot be changed on an image component at runtime
  public void setByReference(boolean b) {
    // double check that changing is necessary
    if (b != images[0].isByReference()) {
      // these are the same for all images so get info once
      int format = images[0].getFormat();
      boolean yUp = images[0].isYUp();
      // reset yUp on all the images
      // have to new ImageComponents because cannot set value
      for (int i = 0; i < images.length; i++) {
        // if the bImage type does not match currentType and we are
        // setting
        // to byRef we need to convert it
        BufferedImage bImage = images[i].getImage();
        if (bImage.getType() != currentType && b) {
          //    bImage = ImageOps.convertImage(bImage, currentType);
          if (currentType != BufferedImage.TYPE_CUSTOM) {
            bImage = ImageOps.convertImage(bImage, currentType);
          } else if (customType == this.TYPE_CUSTOM_RGBA) {
            bImage = ImageOps.convertToCustomRGBA(bImage);
          } else {
            bImage = ImageOps.convertToCustomRGB(bImage);
          }
        }
        images[i] = new ImageComponent2D(format, bImage, b, yUp);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
    }
  }
  // make a new wakeup criterion object based on the new delay time
  public void setFrameDelay(int delay) {
    wakeupC = new WakeupOnElapsedFrames(delay);
  }
  //change the type of image
  public void setImageType(int newType) {
    currentType = newType;
    // only need to change the images if we are byRef otherwise will change
    // them when we chnage to byRef
    if (images[0].isByReference() == true) {
      // this information is the same for all
      int format = images[0].getFormat();
      boolean yUp = images[0].isYUp();
      boolean byRef = true;
      for (int i = 0; i < images.length; i++) {
        BufferedImage bImage = images[i].getImage();
        bImage = ImageOps.convertImage(bImage, currentType);
        images[i] = new ImageComponent2D(format, bImage, byRef, yUp);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
    }
  }
  public void setImageTypeCustomRGBA() {
    currentType = BufferedImage.TYPE_CUSTOM;
    customType = this.TYPE_CUSTOM_RGBA;
    // only need to change images if we are byRef otherwise will change
    // them when we change to byRef
    if (images[0].isByReference()) {
      // this information is the same for all
      int format = images[0].getFormat();
      boolean yUp = images[0].isYUp();
      boolean byRef = true;
      for (int i = 0; i < images.length; i++) {
        BufferedImage bImage = images[i].getImage();
        bImage = ImageOps.convertToCustomRGBA(bImage);
        images[i] = new ImageComponent2D(format, bImage, byRef, yUp);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
    }
  }
  public void setImageTypeCustomRGB() {
    currentType = BufferedImage.TYPE_CUSTOM;
    customType = this.TYPE_CUSTOM_RGB;
    // only need to change images if we are byRef otherwise will change
    // them when we change to byRef
    if (images[0].isByReference()) {
      // this information is the same for all
      int format = images[0].getFormat();
      boolean yUp = images[0].isYUp();
      boolean byRef = true;
      for (int i = 0; i < images.length; i++) {
        BufferedImage bImage = images[i].getImage();
        bImage = ImageOps.convertToCustomRGB(bImage);
        images[i] = new ImageComponent2D(format, bImage, byRef, yUp);
        images[i].setCapability(ImageComponent.ALLOW_IMAGE_READ);
        images[i].setCapability(ImageComponent.ALLOW_FORMAT_READ);
      }
    }
  }
}
class Tetrahedron extends Shape3D {
  private static final float sqrt3 = (float) Math.sqrt(3.0);
  private static final float sqrt3_3 = sqrt3 / 3.0f;
  private static final float sqrt24_3 = (float) Math.sqrt(24.0) / 3.0f;
  private static final float ycenter = 0.5f * sqrt24_3;
  private static final float zcenter = -sqrt3_3;
  private static final Point3f p1 = new Point3f(-1.0f, -ycenter, -zcenter);
  private static final Point3f p2 = new Point3f(1.0f, -ycenter, -zcenter);
  private static final Point3f p3 = new Point3f(0.0f, -ycenter, -sqrt3
      - zcenter);
  private static final Point3f p4 = new Point3f(0.0f, sqrt24_3 - ycenter,
      0.0f);
  private static final Point3f[] verts = { p1, p2, p4, // front face
      p1, p4, p3, // left, back face
      p2, p3, p4, // right, back face
      p1, p3, p2, // bottom face
  };
  private Point2f texCoord[] = { new Point2f(-0.25f, 0.0f),
      new Point2f(1.25f, 0.0f), new Point2f(0.5f, 2.0f), };
  private TriangleArray geometryByRef;
  private TriangleArray geometryByCopy;
  // for geometry by reference
  private Point3f[] verticesArray = new Point3f[12];
  private TexCoord2f[] textureCoordsArray = new TexCoord2f[12];
  private Vector3f[] normalsArray = new Vector3f[12];
  // default to geometry by copy
  public Tetrahedron() {
    this(false);
  }
  // creates a tetrahedron with geometry by reference or by copy depending on
  // the byRef parameter
  public Tetrahedron(boolean byRef) {
    if (byRef) {
      createGeometryByRef();
      this.setGeometry(geometryByRef);
    } else {
      createGeometryByCopy();
      this.setGeometry(geometryByCopy);
    }
    this.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
    this.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
    setAppearance(new Appearance());
  }
  // create the geometry by reference and
  // store it in the geometryByRef variable
  public void createGeometryByRef() {
    //    System.out.println("createGeometryByRef");
    geometryByRef = new TriangleArray(12, TriangleArray.COORDINATES
        | TriangleArray.NORMALS | TriangleArray.TEXTURE_COORDINATE_2
        | TriangleArray.BY_REFERENCE);
    int i;
    // the coordinates
    for (i = 0; i < 12; i++) {
      verticesArray[i] = new Point3f(verts[i]);
    }
    geometryByRef.setCoordRef3f(verticesArray);
    //    System.out.println("coordinates set");
    //    Point3f[] temp1 = geometryByRef.getCoordRef3f();
    //    for (i = 0; i < 12; i++) {
    //       System.out.println(temp1[i]);
    //    }
    // the texture coordinates
    for (i = 0; i < 12; i++) {
      textureCoordsArray[i] = new TexCoord2f(texCoord[i % 3]);
    }
    geometryByRef.setTexCoordRef2f(0, textureCoordsArray);
    //    System.out.println("texture coords set");
    //    TexCoord2f[] temp2 = geometryByRef.getTexCoordRef2f(0);
    //    for (i = 0; i < 12; i++) {
    //      System.out.println(temp2[i]);
    //    }
    // the normals
    Vector3f normal = new Vector3f();
    Vector3f v1 = new Vector3f();
    Vector3f v2 = new Vector3f();
    Point3f[] pts = new Point3f[3];
    for (int face = 0; face < 4; face++) {
      pts[0] = new Point3f(verts[face * 3]);
      pts[1] = new Point3f(verts[face * 3 + 1]);
      pts[2] = new Point3f(verts[face * 3 + 2]);
      v1.sub(pts[1], pts[0]);
      v2.sub(pts[2], pts[0]);
      normal.cross(v1, v2);
      normal.normalize();
      for (i = 0; i < 3; i++) {
        normalsArray[face * 3 + i] = new Vector3f(normal);
      }
    }
    geometryByRef.setNormalRef3f(normalsArray);
    //    System.out.println("normals set");
    //    Vector3f[] temp3 = geometryByRef.getNormalRef3f();
    //    for (i = 0; i < 12; i++) {
    //      System.out.println(temp3[i]);
    //    }
  }
  // create the geometry by copy and store it in the geometryByCopy variable
  public void createGeometryByCopy() {
    int i;
    geometryByCopy = new TriangleArray(12, TriangleArray.COORDINATES
        | TriangleArray.NORMALS | TriangleArray.TEXTURE_COORDINATE_2);
    geometryByCopy.setCoordinates(0, verts);
    for (i = 0; i < 12; i++) {
      geometryByCopy.setTextureCoordinate(0, i, new TexCoord2f(
          texCoord[i % 3]));
    }
    int face;
    Vector3f normal = new Vector3f();
    Vector3f v1 = new Vector3f();
    Vector3f v2 = new Vector3f();
    Point3f[] pts = new Point3f[3];
    for (i = 0; i < 3; i++)
      pts[i] = new Point3f();
    for (face = 0; face < 4; face++) {
      geometryByCopy.getCoordinates(face * 3, pts);
      v1.sub(pts[1], pts[0]);
      v2.sub(pts[2], pts[0]);
      normal.cross(v1, v2);
      normal.normalize();
      for (i = 0; i < 3; i++) {
        geometryByCopy.setNormal((face * 3 + i), normal);
      }
    }
  }
  // set the geometry to geometryByRef or geometryByCopy depending on the
  // parameter. Create geometryByRef or geometryByCopy if necessary
  public void setByReference(boolean b) {
    //    System.out.println("Tetrahedron.setByReference " + b);
    // by reference is true
    if (b) {
      // if there is no geometryByRef, create it
      if (geometryByRef == null) {
        createGeometryByRef();
      }
      // set the geometry
      this.setGeometry(geometryByRef);
    }
    // by reference is false
    else {
      // if there is no geometryByCopy, create it
      if (geometryByCopy == null) {
        createGeometryByCopy();
      }
      // set the geometry
      this.setGeometry(geometryByCopy);
    }
  }
}
//some useful, static image operations
class ImageOps {
  // flip the image
  public static void flipImage(BufferedImage bImage) {
    int width = bImage.getWidth();
    int height = bImage.getHeight();
    int[] rgbArray = new int[width * height];
    bImage.getRGB(0, 0, width, height, rgbArray, 0, width);
    int[] tempArray = new int[width * height];
    int y2 = 0;
    for (int y = height - 1; y >= 0; y--) {
      for (int x = 0; x < width; x++) {
        tempArray[y2 * width + x] = rgbArray[y * width + x];
      }
      y2++;
    }
    bImage.setRGB(0, 0, width, height, tempArray, 0, width);
  }
  // convert the image to a specified BufferedImage type and return it
  public static BufferedImage convertImage(BufferedImage bImage, int type) {
    int width = bImage.getWidth();
    int height = bImage.getHeight();
    BufferedImage newImage = new BufferedImage(width, height, type);
    int[] rgbArray = new int[width * height];
    bImage.getRGB(0, 0, width, height, rgbArray, 0, width);
    newImage.setRGB(0, 0, width, height, rgbArray, 0, width);
    return newImage;
  }
  // print out some of the types of BufferedImages
  static void printType(BufferedImage bImage) {
    int type = bImage.getType();
    if (type == BufferedImage.TYPE_4BYTE_ABGR) {
      System.out.println("TYPE_4BYTE_ABGR");
    } else if (type == BufferedImage.TYPE_INT_ARGB) {
      System.out.println("TYPE_INT_ARGB");
    } else if (type == BufferedImage.TYPE_3BYTE_BGR) {
      System.out.println("TYPE_3BYTE_BGR");
    } else if (type == BufferedImage.TYPE_CUSTOM) {
      System.out.println("TYPE_CUSTOM");
    } else
      System.out.println(type);
  }
  public static BufferedImage convertToCustomRGBA(BufferedImage bImage) {
    if (bImage.getType() != BufferedImage.TYPE_INT_ARGB) {
      ImageOps.convertImage(bImage, BufferedImage.TYPE_INT_ARGB);
    }
    int width = bImage.getWidth();
    int height = bImage.getHeight();
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    int[] nBits = { 8, 8, 8, 8 };
    ColorModel cm = new ComponentColorModel(cs, nBits, true, false,
        Transparency.OPAQUE, 0);
    int[] bandOffset = { 0, 1, 2, 3 };
    WritableRaster newRaster = Raster.createInterleavedRaster(
        DataBuffer.TYPE_BYTE, width, height, width * 4, 4, bandOffset,
        null);
    byte[] byteData = ((DataBufferByte) newRaster.getDataBuffer())
        .getData();
    Raster origRaster = bImage.getData();
    int[] pixel = new int[4];
    int k = 0;
    for (int j = 0; j < height; j++) {
      for (int i = 0; i < width; i++) {
        pixel = origRaster.getPixel(i, j, pixel);
        byteData[k++] = (byte) (pixel[0]);
        byteData[k++] = (byte) (pixel[1]);
        byteData[k++] = (byte) (pixel[2]);
        byteData[k++] = (byte) (pixel[3]);
      }
    }
    BufferedImage newImage = new BufferedImage(cm, newRaster, false, null);
    //  if (newImage.getType() == BufferedImage.TYPE_CUSTOM) {
    //    System.out.println("Type is custom");
    //  }
    return newImage;
  }
  public static BufferedImage convertToCustomRGB(BufferedImage bImage) {
    if (bImage.getType() != BufferedImage.TYPE_INT_ARGB) {
      ImageOps.convertImage(bImage, BufferedImage.TYPE_INT_ARGB);
    }
    int width = bImage.getWidth();
    int height = bImage.getHeight();
    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    int[] nBits = { 8, 8, 8 };
    ColorModel cm = new ComponentColorModel(cs, nBits, false, false,
        Transparency.OPAQUE, 0);
    int[] bandOffset = { 0, 1, 2 };
    WritableRaster newRaster = Raster.createInterleavedRaster(
        DataBuffer.TYPE_BYTE, width, height, width * 3, 3, bandOffset,
        null);
    byte[] byteData = ((DataBufferByte) newRaster.getDataBuffer())
        .getData();
    Raster origRaster = bImage.getData();
    int[] pixel = new int[4];
    int k = 0;
    for (int j = 0; j < height; j++) {
      for (int i = 0; i < width; i++) {
        pixel = origRaster.getPixel(i, j, pixel);
        byteData[k++] = (byte) (pixel[0]);
        byteData[k++] = (byte) (pixel[1]);
        byteData[k++] = (byte) (pixel[2]);
      }
    }
    BufferedImage newImage = new BufferedImage(cm, newRaster, false, null);
    //  if (newImage.getType() == BufferedImage.TYPE_CUSTOM) {
    //    System.out.println("Type is custom");
    //  }
    return newImage;
  }
}