TextureByReference.java Source code

Java tutorial

Introduction

Here is the source code for TextureByReference.java

Source

/*
 * @(#)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;
    }
}