Belajar Komputer

Belajar komputer itu mudah dan gratis

Membuat aplikasi openGL dengan menggunakan java


Kemarin saya dapat tugas kuliah untuk pengganti UAS untuk mata kuliah komputer grafik :D nah tugasnya itu kita harus membuat aplikasi yang ada openGL nya :D nah untuk itu saya menggunakan Java OpenGL (JOGL) sebagai pluginsnya dan memakai netbeans sebagai aplikasi pembangunnya dan tentu saja menggunakan java sebagai bahasa pemrogramannya :D

Nanti saya akan memberikan source codenya :D memberikan file executable :D dan memberikan beberapa cara penggunaanya plus beberapa hasil screenshot aplikasinya :D semoga bermanfaat :D

class Demo.java

package demos.common;

import javax.media.opengl.*;

public abstract class Demo implements GLEventListener {
  protected DemoListener demoListener;
  private boolean doShutdown = true;

  public void setDemoListener(DemoListener listener) {
    this.demoListener = listener;
  }

  // Override this with any other cleanup actions
  public void shutdownDemo() {
    // Execute only once
    boolean shouldDoShutdown = doShutdown;
    doShutdown = false;
    if (shouldDoShutdown) {
      demoListener.shutdownDemo();
    }
  }
}

class DemoListener.java

package demos.common;

/** Defines certain events demos can send. Different harnesses
    may respond differently to these events. */
public interface DemoListener {
  /** Indicates that the demo wants to be terminated. */
  public void shutdownDemo();

  /** Indicates that a repaint should be scheduled later. */
  public void repaint();
}

class JOGLFlyingTextDemo.java

package demos.j2d;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.event.*;
import java.awt.image.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.*;
import com.sun.opengl.util.j2d.*;
import com.sun.opengl.util.texture.*;

import demos.common.*;
import demos.util.*;
import gleem.linalg.*;

/** Illustrates more advanced use of the TextRenderer class; shows how
    to do animated translated and rotated text as well as a drop
    shadow effect. */

public class JOGLFlyingTextDemo extends Demo {
  public static void main(String[] args) {
    JFrame frame = new JFrame("Flying Text");
    frame.getContentPane().setLayout(new BorderLayout());

    GLCanvas canvas = new GLCanvas();
    final JOGLFlyingTextDemo demo = new JOGLFlyingTextDemo();

    canvas.addGLEventListener(demo);
    frame.getContentPane().add(canvas, BorderLayout.CENTER);
    frame.getContentPane().add(demo.buildGUI(), BorderLayout.NORTH);

    DisplayMode mode =
      GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();

    frame.setSize((int) (0.75f * mode.getWidth()),
                  (int) (0.75f * mode.getHeight()));

    final Animator animator = new Animator(canvas);
    frame.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          // Run this on another thread than the AWT event queue to
          // make sure the call to Animator.stop() completes before
          // exiting
          new Thread(new Runnable() {
              public void run() {
                animator.stop();
                System.exit(0);
              }
            }).start();
        }
      });
    frame.show();
    animator.start();
  }

  // Put a little physics on the text to make it look nicer
  private static final float INIT_ANG_VEL_MAG = 0.3f;
  private static final float INIT_VEL_MAG = 400.0f;
  private static final int   DEFAULT_DROP_SHADOW_DIST = 20;

  // Information about each piece of text
  private static class TextInfo {
    float angularVelocity;
    Vec2f velocity;

    float angle;
    Vec2f position;

    float h;
    float s;
    float v;

    // Cycle the saturation
    float curTime;

    // Cache of the RGB color
    float r;
    float g;
    float b;

    String text;
  }

  private List/*<TextInfo>*/ textInfo = new ArrayList/*<TextInfo>*/();
  private int dropShadowDistance = DEFAULT_DROP_SHADOW_DIST;
  private Time time;
  private Texture backgroundTexture;
  private TextRenderer renderer;
  private Random random = new Random();
  private GLU glu = new GLU();
  private int width;
  private int height;

  private int maxTextWidth;

  private FPSCounter fps;

  public Container buildGUI() {
    // Create gui
    JPanel panel = new JPanel();
    JButton button = new JButton("Less Text");
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          lessText();
        }
      });
    panel.add(button);
    final JSlider slider = new JSlider(JSlider.HORIZONTAL,
                                       getMinDropShadowDistance(),
                                       getMaxDropShadowDistance(),
                                       getDropShadowDistance());
    slider.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent e) {
          setDropShadowDistance(slider.getValue());
        }
      });
    panel.add(slider);
    button = new JButton("More Text");
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          moreText();
        }
      });
    panel.add(button);
    return panel;
  }

  public void moreText() {
    int numToAdd = (int) (textInfo.size() * 0.5f);
    if (numToAdd == 0)
      numToAdd = 1;
    for (int i = 0; i < numToAdd; i++) {
      textInfo.add(randomTextInfo());
    }
  }

  public void lessText() {
    if (textInfo.size() == 1)
      return;
    int numToRemove = textInfo.size() / 3;
    if (numToRemove == 0)
      numToRemove = 1;
    for (int i = 0; i < numToRemove; i++) {
      textInfo.remove(textInfo.size() - 1);
    }
  }

  public int getDropShadowDistance() {
    return dropShadowDistance;
  }

  public int getMinDropShadowDistance() {
    return 1;
  }

  public int getMaxDropShadowDistance() {
    return 30;
  }

  public void setDropShadowDistance(int dist) {
    dropShadowDistance = dist;
  }

  public void init(GLAutoDrawable drawable) {
    // Create the background texture
    BufferedImage bgImage = new BufferedImage(2, 2, BufferedImage.TYPE_BYTE_GRAY);
    Graphics2D g = bgImage.createGraphics();
    g.setColor(new Color(0.3f, 0.3f, 0.3f));
    g.fillRect(0, 0, 2, 2);
    g.setColor(new Color(0.7f, 0.7f, 0.7f));
    g.fillRect(0, 0, 1, 1);
    g.fillRect(1, 1, 1, 1);
    g.dispose();
    backgroundTexture = TextureIO.newTexture(bgImage, false);
    backgroundTexture.bind();
    backgroundTexture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
    backgroundTexture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
    backgroundTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_S,     GL.GL_REPEAT);
    backgroundTexture.setTexParameteri(GL.GL_TEXTURE_WRAP_T,     GL.GL_REPEAT);

    // Create the text renderer
    renderer = new TextRenderer(new Font("Serif", Font.PLAIN, 72), true, true);

    // Create the FPS counter
    fps = new FPSCounter(drawable, 36);

    width = drawable.getWidth();
    height = drawable.getWidth();

    // Compute maximum width of text we're going to draw to avoid
    // popping in/out at edges
    maxTextWidth = (int) renderer.getBounds("10106036").getWidth();
    maxTextWidth = Math.max(maxTextWidth, (int) renderer.getBounds("OpenGL").getWidth());

    // Create random text
    textInfo.clear();
    for (int i = 0; i < 100; i++) {
      textInfo.add(randomTextInfo());
    }

    time = new SystemTime();
    ((SystemTime) time).rebase();

    // Set up properties; note we don't need the depth buffer in this demo
    GL gl = drawable.getGL();
    gl.glDisable(GL.GL_DEPTH_TEST);
    // Turn off vsync if we can
    gl.setSwapInterval(0);
  }

  public void display(GLAutoDrawable drawable) {
    time.update();

    // Update velocities and positions of all text
    float deltaT = (float) time.deltaT();
    Vec2f tmp = new Vec2f();
    for (Iterator iter = textInfo.iterator(); iter.hasNext(); ) {
      TextInfo info = (TextInfo) iter.next();

      // Randomize things a little bit at run time
      if (random.nextInt(1000) == 0) {
        info.angularVelocity = INIT_ANG_VEL_MAG * (randomAngle() - 180);
        info.velocity = randomVelocityVec2f(INIT_VEL_MAG, INIT_VEL_MAG);
      }

      // Now update angles and positions
      info.angle += info.angularVelocity * deltaT;
      tmp.set(info.velocity);
      tmp.scale(deltaT);
      info.position.add(tmp);

      // Update color
      info.curTime += deltaT;
      if (info.curTime > 2 * Math.PI) {
        info.curTime -= 2 * Math.PI;
      }
      int rgb = Color.HSBtoRGB(info.h,
                               (float) (0.5 * (1 + Math.sin(info.curTime)) * info.s),
                               info.v);
      info.r = ((rgb >> 16) & 0xFF) / 255.0f;
      info.g = ((rgb >>  8) & 0xFF) / 255.0f;
      info.b = ( rgb        & 0xFF) / 255.0f;

      // Wrap angles and positions
      if (info.angle < 0) {
        info.angle += 360;
      } else if (info.angle > 360) {
        info.angle -= 360;
      }
      // Use maxTextWidth to avoid popping in/out at edges
      // Would be better to do oriented bounding rectangle computation
      if (info.position.x() < -maxTextWidth) {
        info.position.setX(info.position.x() + drawable.getWidth() + 2 * maxTextWidth);
      } else if (info.position.x() > drawable.getWidth() + maxTextWidth) {
        info.position.setX(info.position.x() - drawable.getWidth() - 2 * maxTextWidth);
      }
      if (info.position.y() < -maxTextWidth) {
        info.position.setY(info.position.y() + drawable.getHeight() + 2 * maxTextWidth);
      } else if (info.position.y() > drawable.getHeight() + maxTextWidth) {
        info.position.setY(info.position.y() - drawable.getHeight() - 2 * maxTextWidth);
      }
    }

    GL gl = drawable.getGL();
    gl.glClear(GL.GL_COLOR_BUFFER_BIT);
    gl.glMatrixMode(GL.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluOrtho2D(0, drawable.getWidth(), 0, drawable.getHeight());
    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glLoadIdentity();

    // Draw the background texture
    backgroundTexture.enable();
    backgroundTexture.bind();
    TextureCoords coords = backgroundTexture.getImageTexCoords();
    int w = drawable.getWidth();
    int h = drawable.getHeight();
    float fw = w / 100.0f;
    float fh = h / 100.0f;
    gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
    gl.glBegin(GL.GL_QUADS);
    gl.glTexCoord2f(fw * coords.left(), fh * coords.bottom());
    gl.glVertex3f(0, 0, 0);
    gl.glTexCoord2f(fw * coords.right(), fh * coords.bottom());
    gl.glVertex3f(w, 0, 0);
    gl.glTexCoord2f(fw * coords.right(), fh * coords.top());
    gl.glVertex3f(w, h, 0);
    gl.glTexCoord2f(fw * coords.left(), fh * coords.top());
    gl.glVertex3f(0, h, 0);
    gl.glEnd();
    backgroundTexture.disable();

    // Render all text
    renderer.beginRendering(drawable.getWidth(), drawable.getHeight());

    // Note we're doing some slightly fancy stuff to position the text.
    // We tell the text renderer to render the text at the origin, and
    // manipulate the modelview matrix to put the text where we want.

    gl.glMatrixMode(GL.GL_MODELVIEW);

    // First render drop shadows
    renderer.setColor(0, 0, 0, 0.5f);
    for (Iterator iter = textInfo.iterator(); iter.hasNext(); ) {
      TextInfo info = (TextInfo) iter.next();
      gl.glLoadIdentity();
      gl.glTranslatef(info.position.x() + dropShadowDistance,
                      info.position.y() - dropShadowDistance,
                      0);
      gl.glRotatef(info.angle, 0, 0, 1);
      renderer.draw(info.text, 0, 0);
      // We need to call flush() only because we're modifying the modelview matrix
      renderer.flush();
    }

    // Now render the actual text
    for (Iterator iter = textInfo.iterator(); iter.hasNext(); ) {
      TextInfo info = (TextInfo) iter.next();
      gl.glLoadIdentity();
      gl.glTranslatef(info.position.x(),
                      info.position.y(),
                      0);
      gl.glRotatef(info.angle, 0, 0, 1);
      renderer.setColor(info.r, info.g, info.b, 1);
      renderer.draw(info.text, 0, 0);
      // We need to call flush() only because we're modifying the modelview matrix
      renderer.flush();
    }

    renderer.endRendering();

    // Use the FPS renderer last to render the FPS
    fps.draw();
  }

  public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    this.width = width;
    this.height = height;
  }

  public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}

  //----------------------------------------------------------------------
  // Internals only below this point
  //

  private TextInfo randomTextInfo() {
    TextInfo info = new TextInfo();
    info.text = randomString();
    info.angle = randomAngle();
    info.position = randomVec2f(width, height);

    info.angularVelocity = INIT_ANG_VEL_MAG * (randomAngle() - 180);
    info.velocity = randomVelocityVec2f(INIT_VEL_MAG, INIT_VEL_MAG);

    Color c = randomColor();
    float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
    info.h = hsb[0];
    info.s = hsb[1];
    info.v = hsb[2];
    info.curTime = (float) (2 * Math.PI * random.nextFloat());
    return info;
  }

  private String randomString() {
    switch (random.nextInt(3)) {
      case 0:
        return "OpenGL";
      case 1:
        return "10106036";
      default:
        return "Dimas Irawan";
    }
  }

  private float randomAngle() {
    return 360.0f * random.nextFloat();
  }

  private Vec2f randomVec2f(float x, float y) {
    return new Vec2f(x * random.nextFloat(),
                     y * random.nextFloat());
  }

  private Vec2f randomVelocityVec2f(float x, float y) {
    return new Vec2f(x * (random.nextFloat() - 0.5f),
                     y * (random.nextFloat() - 0.5f));
  }

  private Color randomColor() {
    // Get a bright and saturated color
    float r = 0;
    float g = 0;
    float b = 0;
    float s = 0;
    do {
      r = random.nextFloat();
      g = random.nextFloat();
      b = random.nextFloat();

      float[] hsb = Color.RGBtoHSB((int) (255.0f * r),
                                   (int) (255.0f * g),
                                   (int) (255.0f * b), null);
      s = hsb[1];
    } while ((r < 0.8f && g < 0.8f && b < 0.8f) ||
             s < 0.8f);
    return new Color(r, g, b);
  }
}

class Bunny.java

package demos.util;

import java.io.*;

import javax.media.opengl.*;

public class Bunny {

  /** Generates and returns a display list for the bunny model. */
  public static int gen3DObjectList(GL gl) throws IOException {
    StreamTokenizer tok = new StreamTokenizer(new BufferedReader(new InputStreamReader(
      Bunny.class.getClassLoader().getResourceAsStream("demos/data/models/bunny.txt"))));
    // Reset tokenizer's syntax so numbers are not parsed
    tok.resetSyntax();
    tok.wordChars('a', 'z');
    tok.wordChars('A', 'Z');
    tok.wordChars('0', '9');
    tok.wordChars('-', '-');
    tok.wordChars('.', '.');
    tok.wordChars(128 + 32, 255);
    tok.whitespaceChars(0, ' ');
    tok.whitespaceChars(',', ',');
    tok.whitespaceChars('{', '{');
    tok.whitespaceChars('}', '}');
    tok.commentChar('/');
    tok.quoteChar('"');
    tok.quoteChar('\'');
    tok.slashSlashComments(true);
    tok.slashStarComments(true);

    // Read in file
    int numFaceIndices = nextInt(tok, "number of face indices");
    short[] faceIndices = new short[numFaceIndices * 6];
    for (int i = 0; i < numFaceIndices * 6; i++) {
      faceIndices[i] = (short) nextInt(tok, "face index");
    }
    int numVertices = nextInt(tok, "number of vertices");
    float[] vertices = new float[numVertices * 3];
    for (int i = 0; i < numVertices * 3; i++) {
      vertices[i] = nextFloat(tok, "vertex");
    }
    int numNormals = nextInt(tok, "number of normals");
    float[] normals = new float[numNormals * 3];
    for (int i = 0; i < numNormals * 3; i++) {
      normals[i] = nextFloat(tok, "normal");
    }

    int lid = gl.glGenLists(1);
    gl.glNewList(lid, GL.GL_COMPILE);

    gl.glBegin(GL.GL_TRIANGLES);
    for (int i = 0; i < faceIndices.length; i += 6) {
      for (int j = 0; j < 3; j++) {
        int vi = faceIndices[i + j];
        int ni = faceIndices[i + j + 3];
        gl.glNormal3f(normals[3 * ni],
                      normals[3 * ni + 1],
                      normals[3 * ni + 2]);
        gl.glVertex3f(vertices[3 * vi],
                      vertices[3 * vi + 1],
                      vertices[3 * vi + 2]);
      }
    }
    gl.glEnd();

    gl.glEndList();
    return lid;
  }

  private static int nextInt(StreamTokenizer tok, String error) throws IOException {
    if (tok.nextToken() != StreamTokenizer.TT_WORD) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
    try {
      return Integer.parseInt(tok.sval);
    } catch (NumberFormatException e) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
  }

  private static float nextFloat(StreamTokenizer tok, String error) throws IOException {
    if (tok.nextToken() != StreamTokenizer.TT_WORD) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
    try {
      return Float.parseFloat(tok.sval);
    } catch (NumberFormatException e) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
  }
}

class Cubemap.java

package demos.util;

import java.io.*;

import javax.media.opengl.*;
import com.sun.opengl.util.*;
import com.sun.opengl.util.texture.*;

/** Helper class for loading cubemaps from a set of textures. */

public class Cubemap {
  private static final String[] suffixes = { "posx", "negx", "posy", "negy", "posz", "negz" };
  private static final int[] targets = { GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X,
                                         GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
                                         GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                                         GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                                         GL.GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
                                         GL.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };

  public static Texture loadFromStreams(ClassLoader scope,
                                        String basename,
                                        String suffix,
                                        boolean mipmapped) throws IOException, GLException {
    Texture cubemap = TextureIO.newTexture(GL.GL_TEXTURE_CUBE_MAP);

    for (int i = 0; i < suffixes.length; i++) {
      String resourceName = basename + suffixes[i] + "." + suffix;
      TextureData data = TextureIO.newTextureData(scope.getResourceAsStream(resourceName),
                                                  mipmapped,
                                                  FileUtil.getFileSuffix(resourceName));
      if (data == null) {
        throw new IOException("Unable to load texture " + resourceName);
      }
      cubemap.updateImage(data, targets[i]);
    }

    return cubemap;
  }
}

class DurationTime.java

package demos.util;

/** Simple class for helping measure frames-per-second. */

public class DurationTimer {
  private long startTime;
  private long accumulatedTime;

  public void reset() {
    accumulatedTime = 0;
  }

  public void start() {
    startTime = System.currentTimeMillis();
  }

  public void stop() {
    long curTime = System.currentTimeMillis();
    accumulatedTime += (curTime - startTime);
  }

  public long getDuration() {
    return accumulatedTime;
  }

  public float getDurationAsSeconds() {
    return (float) accumulatedTime / 1000.0f;
  }
}

class DxTex.java

package demos.util;

import java.io.*;
import java.nio.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;

import com.sun.opengl.util.texture.spi.*;

/** Simplified clone of DxTex tool from the DirectX SDK, written in
    Java using the DDSImage; tests fetching of texture data */

public class DxTex {
  private InternalFrameListener frameListener;
  private File defaultDirectory;
  private JDesktopPane desktop;
  private static String endl = System.getProperty("line.separator");
  private JMenu mipMapMenu;

  public static void main(String[] args) {
    new DxTex().run(args);
  }

  private void run(String[] args) {
    defaultDirectory = new File(System.getProperty("user.dir"));
    JFrame frame = new JFrame("DirectX Texture Tool");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    JMenuBar menuBar = new JMenuBar();
    JMenu menu = createMenu("File", 'F', 0);
    JMenuItem item =
      createMenuItem("Open...",
                     new ActionListener() {
                         public void actionPerformed(ActionEvent e) {
                           openFile();
                         }
                       },
                     KeyEvent.VK_O, InputEvent.CTRL_MASK,
                     'O', 0);
    menu.add(item);
    item =
      createMenuItem("Exit",
                     new ActionListener() {
                         public void actionPerformed(ActionEvent e) {
                           System.exit(0);
                         }
                       },
                     KeyEvent.VK_Q, InputEvent.CTRL_MASK,
                     'x', 1);
    menu.add(item);
    menuBar.add(menu);

    menu = createMenu("MipMap", 'M', 0);
    menu.setEnabled(false);
    mipMapMenu = menu;
    menuBar.add(menu);

    frame.setJMenuBar(menuBar);

    desktop = new JDesktopPane();
    frame.getContentPane().add(desktop);
    frame.setSize(640, 480);
    frame.show();

    frameListener = new InternalFrameAdapter() {
        public void internalFrameActivated(InternalFrameEvent e) {
          JInternalFrame ifr = e.getInternalFrame();
          if (ifr instanceof ImageFrame) {
            // Recompute entries in mip map menu
            final ImageFrame frame = (ImageFrame) ifr;
            if (frame.getNumMipMaps() > 0) {
              mipMapMenu.removeAll();
              // Add entries
              for (int i = 0; i < frame.getNumMipMaps(); i++) {
                final int map = i;
                JMenuItem item;
                String title = "Level " + (i + 1);
                ActionListener listener = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                      SwingUtilities.invokeLater(new Runnable() {
                          public void run() {
                            frame.setMipMapLevel(map);
                          }
                        });
                    }
                  };
                if (i < 9) {
                  char c = (char) ('0' + i + 1);
                  item = createMenuItem(title, listener, c, 6);
                } else {
                  item = createMenuItem(title, listener);
                }
                mipMapMenu.add(item);
              }
              mipMapMenu.setEnabled(true);
            } else {
              mipMapMenu.setEnabled(false);
            }
          } else {
            mipMapMenu.setEnabled(false);
          }
        }

        public void internalFrameClosing(InternalFrameEvent e) {
          desktop.remove(e.getInternalFrame());
          desktop.invalidate();
          desktop.validate();
          desktop.repaint();
          // THIS SHOULD NOT BE NECESSARY
          desktop.requestFocus();
        }

        public void internalFrameClosed(InternalFrameEvent e) {
          JInternalFrame ifr = e.getInternalFrame();
          if (ifr instanceof ImageFrame) {
            ((ImageFrame) ifr).close();
          }
        }
      };

    for (int i = 0; i < args.length; i++) {
      final File file = new File(args[i]);
      SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            openFile(file);
          }
        });
    }
  }

  //----------------------------------------------------------------------
  // Actions
  //

  private void openFile() {
    JFileChooser chooser = new JFileChooser(defaultDirectory);
    chooser.setMultiSelectionEnabled(false);
    chooser.addChoosableFileFilter(new javax.swing.filechooser.FileFilter() {
        public boolean accept(File f) {
          return (f.isDirectory() ||
                  f.getName().endsWith(".dds"));
        }

        public String getDescription() {
          return "Texture files (*.dds)";
        }
      });

    int res = chooser.showOpenDialog(null);
    if (res == JFileChooser.APPROVE_OPTION) {
      final File file = chooser.getSelectedFile();
      defaultDirectory = file.getParentFile();
      SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            openFile(file);
          }
        });
    }
  }

  private void openFile(File file) {
    try {
      DDSImage image = DDSImage.read(file);
      showImage(file.getName(), image, 0);
    } catch (IOException e) {
      showMessageDialog("Error while opening file:" + endl +
                        exceptionToString(e),
                        "Error opening file",
                        JOptionPane.WARNING_MESSAGE);
    }
  }

  //----------------------------------------------------------------------
  // Image display
  //

  private void showImage(String filename, DDSImage image, int mipMapLevel) {
    try {
      ImageFrame fr = new ImageFrame(filename, image, mipMapLevel);
      desktop.add(fr);
      fr.show();
    } catch (Exception e) {
      showMessageDialog("Error while loading file:" + endl +
                        exceptionToString(e),
                        "Error loading file",
                        JOptionPane.WARNING_MESSAGE);
    }
  }

  class ImageFrame extends JInternalFrame {
    private String filename;
    private DDSImage image;
    private int mipMapLevel;
    private int curWidth;
    private int curHeight;
    private JLabel label;

    ImageFrame(String filename, DDSImage image, int mipMapLevel) {
      super();
      this.filename = filename;
      this.image = image;

      addInternalFrameListener(frameListener);
      label = new JLabel();
      JScrollPane scroller = new JScrollPane(label);
      getContentPane().add(scroller);
      setSize(400, 400);
      setResizable(true);
      setIconifiable(true);
      setClosable(true);
      setMipMapLevel(mipMapLevel);
    }

    int getNumMipMaps() {
      return image.getNumMipMaps();
    }

    void setMipMapLevel(int level) {
      mipMapLevel = level;
      computeImage();
      resetTitle();
    }

    void close() {
      System.err.println("Closing files");
      image.close();
    }

    private void computeImage() {
      // Get image data
      image.getNumMipMaps();
      DDSImage.ImageInfo info = image.getMipMap(mipMapLevel);
      int width = info.getWidth();
      int height = info.getHeight();
      curWidth = width;
      curHeight = height;
      ByteBuffer data = info.getData();

      // Build ImageIcon out of image data
      BufferedImage img = new BufferedImage(width, height,
                                            BufferedImage.TYPE_3BYTE_BGR);
      WritableRaster dst = img.getRaster();

      int skipSize;
      if (image.getPixelFormat() == DDSImage.D3DFMT_A8R8G8B8) {
        skipSize = 4;
      } else if (image.getPixelFormat() == DDSImage.D3DFMT_R8G8B8) {
        skipSize = 3;
      } else {
        image.close();
        throw new RuntimeException("Unsupported pixel format " + image.getPixelFormat());
      }

      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
          // NOTE: highly suspicious that A comes fourth in
          // A8R8G8B8...not really ARGB, but RGBA (like OpenGL)
          dst.setSample(x, y, 0, data.get(skipSize * (width * y + x) + 2) & 0xFF);
          dst.setSample(x, y, 1, data.get(skipSize * (width * y + x) + 1) & 0xFF);
          dst.setSample(x, y, 2, data.get(skipSize * (width * y + x) + 0) & 0xFF);
        }
      }

      label.setIcon(new ImageIcon(img));
    }

    private void resetTitle() {
      setTitle(filename + " (" + curWidth + "x" + curHeight +
               ", mipmap " + (1 + mipMapLevel) + " of " +
               image.getNumMipMaps() + ")");
    }
  }

  //----------------------------------------------------------------------
  // Menu and menu item creation
  //

  private static JMenu createMenu(String name, char mnemonic, int mnemonicPos) {
    JMenu menu = new JMenu(name);
    menu.setMnemonic(mnemonic);
    menu.setDisplayedMnemonicIndex(mnemonicPos);
    return menu;
  }

  private static JMenuItem createMenuItem(String name, ActionListener l) {
    JMenuItem item = new JMenuItem(name);
    item.addActionListener(l);
    return item;
  }

  private static JMenuItem createMenuItemInternal(String name, ActionListener l, int accelerator, int modifiers) {
    JMenuItem item = createMenuItem(name, l);
    item.setAccelerator(KeyStroke.getKeyStroke(accelerator, modifiers));
    return item;
  }

  private static JMenuItem createMenuItem(String name, ActionListener l, int accelerator) {
    return createMenuItemInternal(name, l, accelerator, 0);
  }

  private static JMenuItem createMenuItem(String name, ActionListener l, char mnemonic, int mnemonicPos) {
    JMenuItem item = createMenuItem(name, l);
    item.setMnemonic(mnemonic);
    item.setDisplayedMnemonicIndex(mnemonicPos);
    return item;
  }

  private static JMenuItem createMenuItem(String name,
                                          ActionListener l,
                                          int accelerator,
                                          int acceleratorMods,
                                          char mnemonic,
                                          int mnemonicPos) {
    JMenuItem item = createMenuItemInternal(name, l, accelerator, acceleratorMods);
    item.setMnemonic(mnemonic);
    item.setDisplayedMnemonicIndex(mnemonicPos);
    return item;
  }

  private void showMessageDialog(final String message, final String title, final int jOptionPaneKind) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          JOptionPane.showInternalMessageDialog(desktop, message, title, jOptionPaneKind);
        }
      });
  }

  private static String exceptionToString(Exception e) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    PrintStream s = new PrintStream(bos);
    e.printStackTrace(s);
    return bos.toString();
  }
}

class FPSCounter.java

package demos.util;

import java.awt.Font;
import java.awt.geom.*;
import java.text.*;

import javax.media.opengl.*;
import com.sun.opengl.util.j2d.*;

/** A simple class which uses the TextRenderer to provide an FPS
    counter overlaid on top of the scene. */

public class FPSCounter {
  // Placement constants
  public static final int UPPER_LEFT  = 1;
  public static final int UPPER_RIGHT = 2;
  public static final int LOWER_LEFT  = 3;
  public static final int LOWER_RIGHT = 4;

  private int textLocation = LOWER_RIGHT;
  private GLDrawable drawable;
  private TextRenderer renderer;
  private DecimalFormat format = new DecimalFormat("####.00");
  private int frameCount;
  private long startTime;
  private String fpsText;
  private int fpsMagnitude;
  private int fpsWidth;
  private int fpsHeight;
  private int fpsOffset;

  /** Creates a new FPSCounter with the given font size. An OpenGL
      context must be current at the time the constructor is called.

      @param drawable the drawable to render the text to
      @param textSize the point size of the font to use
      @throws GLException if an OpenGL context is not current when the constructor is called
  */
  public FPSCounter(GLDrawable drawable, int textSize) throws GLException {
    this(drawable, new Font("SansSerif", Font.BOLD, textSize));
  }

  /** Creates a new FPSCounter with the given font. An OpenGL context
      must be current at the time the constructor is called.

      @param drawable the drawable to render the text to
      @param font the font to use
      @throws GLException if an OpenGL context is not current when the constructor is called
  */
  public FPSCounter(GLDrawable drawable, Font font) throws GLException {
    this(drawable, font, true, true);
  }

  /** Creates a new FPSCounter with the given font and rendering
      attributes. An OpenGL context must be current at the time the
      constructor is called.

      @param drawable the drawable to render the text to
      @param font the font to use
      @param antialiased whether to use antialiased fonts
      @param useFractionalMetrics whether to use fractional font
      @throws GLException if an OpenGL context is not current when the constructor is called
  */
  public FPSCounter(GLDrawable drawable,
                    Font font,
                    boolean antialiased,
                    boolean useFractionalMetrics) throws GLException {
    this.drawable = drawable;
    renderer = new TextRenderer(font, antialiased, useFractionalMetrics);
  }

  /** Gets the relative location where the text of this FPSCounter
      will be drawn: one of UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, or
      LOWER_RIGHT. Defaults to LOWER_RIGHT. */
  public int getTextLocation() {
    return textLocation;
  }

  /** Sets the relative location where the text of this FPSCounter
      will be drawn: one of UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, or
      LOWER_RIGHT. Defaults to LOWER_RIGHT. */
  public void setTextLocation(int textLocation) {
    if (textLocation < UPPER_LEFT || textLocation > LOWER_RIGHT) {
      throw new IllegalArgumentException("textLocation");
    }
    this.textLocation = textLocation;
  }

  /** Changes the current color of this TextRenderer to the supplied
      one, where each component ranges from 0.0f - 1.0f. The alpha
      component, if used, does not need to be premultiplied into the
      color channels as described in the documentation for {@link
      Texture Texture}, although premultiplied colors are used
      internally. The default color is opaque white.

      @param r the red component of the new color
      @param g the green component of the new color
      @param b the blue component of the new color
      @param alpha the alpha component of the new color, 0.0f =
        completely transparent, 1.0f = completely opaque
      @throws GLException If an OpenGL context is not current when this method is called
  */
  public void setColor(float r, float g, float b, float a) throws GLException {
    renderer.setColor(r, g, b, a);
  }

  /** Updates the FPSCounter's internal timer and counter and draws
      the computed FPS. It is assumed this method will be called only
      once per frame.
  */
  public void draw() {
    if (startTime == 0) {
      startTime = System.currentTimeMillis();
    }

    if (++frameCount >= 100) {
      long endTime = System.currentTimeMillis();
      float fps = 100.0f / (float) (endTime - startTime) * 1000;
      recomputeFPSSize(fps);
      frameCount = 0;
      startTime = System.currentTimeMillis();

      fpsText = "FPS: " + format.format(fps);
    }

    if (fpsText != null) {
      renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
      // Figure out the location at which to draw the text
      int x = 0;
      int y = 0;
      switch (textLocation) {
        case UPPER_LEFT:
          x = fpsOffset;
          y = drawable.getHeight() - fpsHeight - fpsOffset;
          break;

        case UPPER_RIGHT:
          x = drawable.getWidth() - fpsWidth - fpsOffset;
          y = drawable.getHeight() - fpsHeight - fpsOffset;
          break;

        case LOWER_LEFT:
          x = fpsOffset;
          y = fpsOffset;
          break;

        case LOWER_RIGHT:
          x = drawable.getWidth() - fpsWidth - fpsOffset;
          y = fpsOffset;
          break;
      }

      renderer.draw(fpsText, x, y);
      renderer.endRendering();
    }
  }

  private void recomputeFPSSize(float fps) {
    String fpsText;
    int fpsMagnitude;
    if (fps >= 10000) {
      fpsText = "10000.00";
      fpsMagnitude = 5;
    } else if (fps >= 1000) {
      fpsText = "1000.00";
      fpsMagnitude = 4;
    } else if (fps >= 100) {
      fpsText = "100.00";
      fpsMagnitude = 3;
    } else if (fps >= 10) {
      fpsText = "10.00";
      fpsMagnitude = 2;
    } else {
      fpsText = "9.00";
      fpsMagnitude = 1;
    }

    if (fpsMagnitude > this.fpsMagnitude) {
      Rectangle2D bounds = renderer.getBounds("FPS: " + fpsText);
      fpsWidth = (int) bounds.getWidth();
      fpsHeight = (int) bounds.getHeight();
      fpsOffset = (int) (fpsHeight * 0.5f);
      this.fpsMagnitude = fpsMagnitude;
    }
  }
}

class FileUtils.java

package demos.util;

import java.io.*;

public class FileUtils {
  public static String loadStreamIntoString(InputStream stream) throws IOException {
    if (stream == null) {
      throw new java.io.IOException("null stream");
    }
    stream = new java.io.BufferedInputStream(stream);
    int avail = stream.available();
    byte[] data = new byte[avail];
    int numRead = 0;
    int pos = 0;
    do {
      if (pos + avail > data.length) {
        byte[] newData = new byte[pos + avail];
        System.arraycopy(data, 0, newData, 0, pos);
        data = newData;
      }
      numRead = stream.read(data, pos, avail);
      if (numRead >= 0) {
        pos += numRead;
      }
      avail = stream.available();
    } while (avail > 0 && numRead >= 0);
    return new String(data, 0, pos, "US-ASCII");
  }
}

class FloatList.java

package demos.util;

/** Growable array of floats. */

public class FloatList {
  private static final int DEFAULT_SIZE = 10;

  private float[] data = new float[DEFAULT_SIZE];
  private int numElements;

  public void add(float f) {
    if (numElements == data.length) {
      resize(1 + numElements);
    }
    data[numElements++] = f;
    assert numElements <= data.length;
  }

  public int size() {
    return numElements;
  }

  public float get(int index) {
    if (index >= numElements) {
      throw new ArrayIndexOutOfBoundsException(index);
    }
    return data[index];
  }

  public void put(int index, float val) {
    if (index >= numElements) {
      throw new ArrayIndexOutOfBoundsException(index);
    }
    data[index] = val;
  }

  public void trim() {
    if (data.length > numElements) {
      float[] newData = new float[numElements];
      System.arraycopy(data, 0, newData, 0, numElements);
      data = newData;
    }
  }

  public float[] getData() {
    return data;
  }

  private void resize(int minCapacity) {
    int newCapacity = 2 * data.length;
    if (newCapacity == 0) {
      newCapacity = DEFAULT_SIZE;
    }
    if (newCapacity < minCapacity) {
      newCapacity = minCapacity;
    }
    float[] newData = new float[newCapacity];
    System.arraycopy(data, 0, newData, 0, data.length);
    data = newData;
  }
}

class IntList.java

package demos.util;

/** Growable array of ints. */

public class IntList {
  private static final int DEFAULT_SIZE = 10;

  private int[] data = new int[DEFAULT_SIZE];
  private int numElements;

  public void add(int f) {
    if (numElements == data.length) {
      resize(1 + numElements);
    }
    data[numElements++] = f;
    assert numElements <= data.length;
  }

  public int size() {
    return numElements;
  }

  public int get(int index) {
    if (index >= numElements) {
      throw new ArrayIndexOutOfBoundsException(index);
    }
    return data[index];
  }

  public void put(int index, int val) {
    if (index >= numElements) {
      throw new ArrayIndexOutOfBoundsException(index);
    }
    data[index] = val;
  }

  public void trim() {
    if (data.length > numElements) {
      int[] newData = new int[numElements];
      System.arraycopy(data, 0, newData, 0, numElements);
      data = newData;
    }
  }

  public int[] getData() {
    return data;
  }

  private void resize(int minCapacity) {
    int newCapacity = 2 * data.length;
    if (newCapacity == 0) {
      newCapacity = DEFAULT_SIZE;
    }
    if (newCapacity < minCapacity) {
      newCapacity = minCapacity;
    }
    int[] newData = new int[newCapacity];
    System.arraycopy(data, 0, newData, 0, data.length);
    data = newData;
  }
}

class MD2.java

package demos.util;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

/** Reader for MD2 models, used by Quake II. */

public class MD2 {
  public static Model loadMD2(String filename) throws IOException {
    List/*<IFrame>*/ ifr = new ArrayList/*<IFrame>*/();
    loadFrames(filename, ifr);
    return computeModel(ifr);
  }

  public static Model loadMD2(InputStream in) throws IOException {
    List/*<IFrame>*/ ifr = new ArrayList/*<IFrame>*/();
    loadFrames(in, ifr);
    return computeModel(ifr);
  }

  public static class FileHeader {
    public int ident;
    public int version;
    public int skinwidth;
    public int skinheight;
    public int framesize;     // byte size of each frame
    public int num_skins;
    public int num_xyz;
    public int num_st;        // greater than num_xyz for seams
    public int num_tris;
    public int num_glcmds;    // dwords in strip/fan command list
    public int num_frames;
    public int ofs_skins;     // each skin is a MAX_SKINNAME string
    public int ofs_st;        // byte offset from start for stverts
    public int ofs_tris;      // offset for dtriangles
    public int ofs_frames;    // offset for first frame
    public int ofs_glcmds;
    public int ofs_end;       // end of file
  };

  public static class FileCompressedVertex {
    public byte[] v = new byte[3]; // scaled byte to fit in frame mins/maxs
    public byte lightnormalindex;
  }

  public static class FileFrame {
    public float[] scale     = new float[3];           // multiply byte verts by this
    public float[] translate = new float[3];           // then add this
    public String name;                                // frame name from grabbing
    public FileCompressedVertex[] verts;               // variable sized
  }

  public static class FileModel {
    public int[] glcmds;
    public FileFrame[] frames;
  }

  public static class PositionNormal implements Cloneable {
    public float x, y, z;
    public float nx, ny, nz;
    public Object clone() {
      try {
        return super.clone();
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
      }
    }
  }

  public static class TexCoord {
    public float s,t;
  }

  public static class Vertex {
    public int pn_index;
    public TexCoord tc = new TexCoord();
  }

  public static class Triangle {
    public Vertex[] v = new Vertex[3];
    public boolean kill;
  }

  public static class WingedEdge {
    public int[] e = new int[2];  // vertex index
    public int[] w = new int[2];  // triangle index: for "open" models, w[1] == -1 on open edges
  }

  public static class Plane implements Cloneable {
    public float a,b,c,d;
    public Object clone() {
      try {
        return super.clone();
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
      }
    }
  }

  public static class Frame implements Cloneable {
    public PositionNormal[] pn;  // [pn_index]
    public Plane[] triplane;     // [tri_num]

    public Object clone() {
      Frame res = new Frame();
      res.pn = new PositionNormal[pn.length];
      for (int i = 0; i < pn.length; i++) {
        res.pn[i] = (PositionNormal) pn[i].clone();
      }
      res.triplane = new Plane[triplane.length];
      for (int i = 0; i < triplane.length; i++) {
        res.triplane[i] = (Plane) triplane[i].clone();
      }
      return res;
    }
  }

  public static class Model {
    public Frame[] f;
    public Triangle[] tri;                   // [tri_num]
    public WingedEdge[] edge;                // [edge_num]
  }

  public static void computePlane(PositionNormal a, PositionNormal b, PositionNormal c, Plane p) {
    float[] v0 = new float[3];
    v0[0] = b.x - a.x;
    v0[1] = b.y - a.y;
    v0[2] = b.z - a.z;
    float[] v1 = new float[3];
    v1[0] = c.x - a.x;
    v1[1] = c.y - a.y;
    v1[2] = c.z - a.z;
    float[] cr = new float[3];
    cr[0] = v0[1] * v1[2] - v0[2] * v1[1];
    cr[1] = v0[2] * v1[0] - v0[0] * v1[2];
    cr[2] = v0[0] * v1[1] - v0[1] * v1[0];
    float l = (float) Math.sqrt(cr[0] * cr[0] + cr[1] * cr[1] + cr[2] * cr[2]);
    if (l == 0) {
      // degenerate triangle
      p.a = p.b = p.c = p.d = 0;
      return;
    }
    p.a = cr[0] / l;
    p.b = cr[1] / l;
    p.c = cr[2] / l;

    p.d = -(p.a * a.x + p.b * a.y + p.c * a.z);  // signed distance of a point on the plane from the origin
  }

  //----------------------------------------------------------------------
  // Internals only below this point
  //

  private static Model computeModel(List/*<IFrame>*/ ifr) throws IOException {
    if (!compareFrames(ifr)) {
      throw new IOException("unsuitable model -- frames aren't same");
    }
    Model m = new Model();
    m.tri = ((IFrame) ifr.get(0)).tri;
    m.f = new Frame[ifr.size()];
    for (int i = 0; i < ifr.size(); i++) {
      Frame f = new Frame();
      m.f[i] = f;
      IFrame it = (IFrame) ifr.get(i);
      f.pn = it.pn;
      computeFramePlanes(m.tri, f);
    }
    computeWingedEdges(m);
    return m;
  }

  private static class IFrame {
    PositionNormal[] pn;
    Triangle[] tri;
  }

  // normal table lifted from Mark Kilgard's md2bump demo
  private static final float[] normalTable = new float[] {
    -0.525731f, 0.000000f, 0.850651f,
    -0.442863f, 0.238856f, 0.864188f,
    -0.295242f, 0.000000f, 0.955423f,
    -0.309017f, 0.500000f, 0.809017f,
    -0.162460f, 0.262866f, 0.951056f,
    0.000000f, 0.000000f, 1.000000f,
    0.000000f, 0.850651f, 0.525731f,
    -0.147621f, 0.716567f, 0.681718f,
    0.147621f, 0.716567f, 0.681718f,
    0.000000f, 0.525731f, 0.850651f,
    0.309017f, 0.500000f, 0.809017f,
    0.525731f, 0.000000f, 0.850651f,
    0.295242f, 0.000000f, 0.955423f,
    0.442863f, 0.238856f, 0.864188f,
    0.162460f, 0.262866f, 0.951056f,
    -0.681718f, 0.147621f, 0.716567f,
    -0.809017f, 0.309017f, 0.500000f,
    -0.587785f, 0.425325f, 0.688191f,
    -0.850651f, 0.525731f, 0.000000f,
    -0.864188f, 0.442863f, 0.238856f,
    -0.716567f, 0.681718f, 0.147621f,
    -0.688191f, 0.587785f, 0.425325f,
    -0.500000f, 0.809017f, 0.309017f,
    -0.238856f, 0.864188f, 0.442863f,
    -0.425325f, 0.688191f, 0.587785f,
    -0.716567f, 0.681718f, -0.147621f,
    -0.500000f, 0.809017f, -0.309017f,
    -0.525731f, 0.850651f, 0.000000f,
    0.000000f, 0.850651f, -0.525731f,
    -0.238856f, 0.864188f, -0.442863f,
    0.000000f, 0.955423f, -0.295242f,
    -0.262866f, 0.951056f, -0.162460f,
    0.000000f, 1.000000f, 0.000000f,
    0.000000f, 0.955423f, 0.295242f,
    -0.262866f, 0.951056f, 0.162460f,
    0.238856f, 0.864188f, 0.442863f,
    0.262866f, 0.951056f, 0.162460f,
    0.500000f, 0.809017f, 0.309017f,
    0.238856f, 0.864188f, -0.442863f,
    0.262866f, 0.951056f, -0.162460f,
    0.500000f, 0.809017f, -0.309017f,
    0.850651f, 0.525731f, 0.000000f,
    0.716567f, 0.681718f, 0.147621f,
    0.716567f, 0.681718f, -0.147621f,
    0.525731f, 0.850651f, 0.000000f,
    0.425325f, 0.688191f, 0.587785f,
    0.864188f, 0.442863f, 0.238856f,
    0.688191f, 0.587785f, 0.425325f,
    0.809017f, 0.309017f, 0.500000f,
    0.681718f, 0.147621f, 0.716567f,
    0.587785f, 0.425325f, 0.688191f,
    0.955423f, 0.295242f, 0.000000f,
    1.000000f, 0.000000f, 0.000000f,
    0.951056f, 0.162460f, 0.262866f,
    0.850651f, -0.525731f, 0.000000f,
    0.955423f, -0.295242f, 0.000000f,
    0.864188f, -0.442863f, 0.238856f,
    0.951056f, -0.162460f, 0.262866f,
    0.809017f, -0.309017f, 0.500000f,
    0.681718f, -0.147621f, 0.716567f,
    0.850651f, 0.000000f, 0.525731f,
    0.864188f, 0.442863f, -0.238856f,
    0.809017f, 0.309017f, -0.500000f,
    0.951056f, 0.162460f, -0.262866f,
    0.525731f, 0.000000f, -0.850651f,
    0.681718f, 0.147621f, -0.716567f,
    0.681718f, -0.147621f, -0.716567f,
    0.850651f, 0.000000f, -0.525731f,
    0.809017f, -0.309017f, -0.500000f,
    0.864188f, -0.442863f, -0.238856f,
    0.951056f, -0.162460f, -0.262866f,
    0.147621f, 0.716567f, -0.681718f,
    0.309017f, 0.500000f, -0.809017f,
    0.425325f, 0.688191f, -0.587785f,
    0.442863f, 0.238856f, -0.864188f,
    0.587785f, 0.425325f, -0.688191f,
    0.688191f, 0.587785f, -0.425325f,
    -0.147621f, 0.716567f, -0.681718f,
    -0.309017f, 0.500000f, -0.809017f,
    0.000000f, 0.525731f, -0.850651f,
    -0.525731f, 0.000000f, -0.850651f,
    -0.442863f, 0.238856f, -0.864188f,
    -0.295242f, 0.000000f, -0.955423f,
    -0.162460f, 0.262866f, -0.951056f,
    0.000000f, 0.000000f, -1.000000f,
    0.295242f, 0.000000f, -0.955423f,
    0.162460f, 0.262866f, -0.951056f,
    -0.442863f, -0.238856f, -0.864188f,
    -0.309017f, -0.500000f, -0.809017f,
    -0.162460f, -0.262866f, -0.951056f,
    0.000000f, -0.850651f, -0.525731f,
    -0.147621f, -0.716567f, -0.681718f,
    0.147621f, -0.716567f, -0.681718f,
    0.000000f, -0.525731f, -0.850651f,
    0.309017f, -0.500000f, -0.809017f,
    0.442863f, -0.238856f, -0.864188f,
    0.162460f, -0.262866f, -0.951056f,
    0.238856f, -0.864188f, -0.442863f,
    0.500000f, -0.809017f, -0.309017f,
    0.425325f, -0.688191f, -0.587785f,
    0.716567f, -0.681718f, -0.147621f,
    0.688191f, -0.587785f, -0.425325f,
    0.587785f, -0.425325f, -0.688191f,
    0.000000f, -0.955423f, -0.295242f,
    0.000000f, -1.000000f, 0.000000f,
    0.262866f, -0.951056f, -0.162460f,
    0.000000f, -0.850651f, 0.525731f,
    0.000000f, -0.955423f, 0.295242f,
    0.238856f, -0.864188f, 0.442863f,
    0.262866f, -0.951056f, 0.162460f,
    0.500000f, -0.809017f, 0.309017f,
    0.716567f, -0.681718f, 0.147621f,
    0.525731f, -0.850651f, 0.000000f,
    -0.238856f, -0.864188f, -0.442863f,
    -0.500000f, -0.809017f, -0.309017f,
    -0.262866f, -0.951056f, -0.162460f,
    -0.850651f, -0.525731f, 0.000000f,
    -0.716567f, -0.681718f, -0.147621f,
    -0.716567f, -0.681718f, 0.147621f,
    -0.525731f, -0.850651f, 0.000000f,
    -0.500000f, -0.809017f, 0.309017f,
    -0.238856f, -0.864188f, 0.442863f,
    -0.262866f, -0.951056f, 0.162460f,
    -0.864188f, -0.442863f, 0.238856f,
    -0.809017f, -0.309017f, 0.500000f,
    -0.688191f, -0.587785f, 0.425325f,
    -0.681718f, -0.147621f, 0.716567f,
    -0.442863f, -0.238856f, 0.864188f,
    -0.587785f, -0.425325f, 0.688191f,
    -0.309017f, -0.500000f, 0.809017f,
    -0.147621f, -0.716567f, 0.681718f,
    -0.425325f, -0.688191f, 0.587785f,
    -0.162460f, -0.262866f, 0.951056f,
    0.442863f, -0.238856f, 0.864188f,
    0.162460f, -0.262866f, 0.951056f,
    0.309017f, -0.500000f, 0.809017f,
    0.147621f, -0.716567f, 0.681718f,
    0.000000f, -0.525731f, 0.850651f,
    0.425325f, -0.688191f, 0.587785f,
    0.587785f, -0.425325f, 0.688191f,
    0.688191f, -0.587785f, 0.425325f,
    -0.955423f, 0.295242f, 0.000000f,
    -0.951056f, 0.162460f, 0.262866f,
    -1.000000f, 0.000000f, 0.000000f,
    -0.850651f, 0.000000f, 0.525731f,
    -0.955423f, -0.295242f, 0.000000f,
    -0.951056f, -0.162460f, 0.262866f,
    -0.864188f, 0.442863f, -0.238856f,
    -0.951056f, 0.162460f, -0.262866f,
    -0.809017f, 0.309017f, -0.500000f,
    -0.864188f, -0.442863f, -0.238856f,
    -0.951056f, -0.162460f, -0.262866f,
    -0.809017f, -0.309017f, -0.500000f,
    -0.681718f, 0.147621f, -0.716567f,
    -0.681718f, -0.147621f, -0.716567f,
    -0.850651f, 0.000000f, -0.525731f,
    -0.688191f, 0.587785f, -0.425325f,
    -0.587785f, 0.425325f, -0.688191f,
    -0.425325f, 0.688191f, -0.587785f,
    -0.425325f, -0.688191f, -0.587785f,
    -0.587785f, -0.425325f, -0.688191f,
    -0.688191f, -0.587785f, -0.425325f
  };

  private static void loadFrames(String filename, List/*<IFrame>*/ md2p) throws IOException {
    FileModel mf = loadMD2File(filename);
    computeFrames(mf, md2p);
  }

  private static void loadFrames(InputStream in, List/*<IFrame>*/ md2p) throws IOException {
    FileModel mf = loadMD2File(in);
    computeFrames(mf, md2p);
  }

  private static void computeFrames(FileModel mf, List/*<IFrame>*/ md2p) throws IOException {
    for (int i = 0; i < mf.frames.length; i++) {
      IFrame f = new IFrame();
      md2p.add(f);
      FileFrame curframe = mf.frames[i];
      f.pn = new PositionNormal[curframe.verts.length];
      for (int j = 0; j < curframe.verts.length; j++) {
        PositionNormal pn = new PositionNormal();
        pn.x = (((curframe.verts[j].v[0] & 0xFF) * curframe.scale[0]) + curframe.translate[0]) * .025f;
        pn.y = (((curframe.verts[j].v[1] & 0xFF) * curframe.scale[1]) + curframe.translate[1]) * .025f;
        pn.z = (((curframe.verts[j].v[2] & 0xFF) * curframe.scale[2]) + curframe.translate[2]) * .025f;
        int normal_index = curframe.verts[j].lightnormalindex & 0xFF;
        pn.nx = normalTable[3 * normal_index + 0];
        pn.ny = normalTable[3 * normal_index + 1];
        pn.nz = normalTable[3 * normal_index + 2];
        f.pn[j] = pn;
      }

      List/*<Triangle>*/ tris = new ArrayList();
      int[] idx = new int[1];
      while (mf.glcmds[idx[0]] != 0) {
        int vertnum;
        boolean is_strip;
        if (mf.glcmds[idx[0]] > 0) {
          vertnum =  mf.glcmds[idx[0]++]; is_strip = true;  // triangle strip
        } else {
          vertnum = -mf.glcmds[idx[0]++]; is_strip = false; // triangle fan
        }

        if (is_strip) {
          Vertex[] prev = new Vertex[2];
          prev[0] = extractVertex(mf.glcmds, idx);
          prev[1] = extractVertex(mf.glcmds, idx);
          for (int j = 2; j < vertnum; j++) {
            Triangle tri = new Triangle();
            if ((j % 2) == 0) {
              tri.v[0] = prev[0];
              tri.v[1] = prev[1];
              tri.v[2] = extractVertex(mf.glcmds, idx);
              prev[0] = tri.v[2];
            } else {
              tri.v[0] = prev[1];
              tri.v[1] = extractVertex(mf.glcmds, idx);
              tri.v[2] = prev[0];
              prev[1] = tri.v[1];
            }
            // swap v[1] and v[2] to fix triangle winding
            Vertex hold = tri.v[1];
            tri.v[1] = tri.v[2];
            tri.v[2] = hold;
            tris.add(tri);
          }
        } else {
          // is fan
          Vertex ctr = extractVertex(mf.glcmds, idx);
          Vertex prev = extractVertex(mf.glcmds, idx);
          for (int j = 2; j < vertnum; j++) {
            Triangle tri = new Triangle();
            tri.v[0] = ctr;
            tri.v[1] = prev;
            tri.v[2] = extractVertex(mf.glcmds, idx);
            prev = tri.v[2];
            // swap v[1] and v[2] to fix triangle winding
            Vertex hold = tri.v[1];
            tri.v[1] = tri.v[2];
            tri.v[2] = hold;
            tris.add(tri);
          }
        }
      }
      f.tri = (Triangle[]) tris.toArray(new Triangle[0]);
    }
  }

  private static FileModel loadMD2File(ByteBuffer buf) throws IOException {
    buf.order(ByteOrder.LITTLE_ENDIAN);
    FileModel md2p = new FileModel();
    FileHeader header = readHeader(buf);
    buf.position(header.ofs_frames);
    readFrames(buf, header, md2p);
    buf.position(header.ofs_glcmds);
    readGLCommands(buf, header, md2p);
    return md2p;
  }

  private static FileModel loadMD2File(InputStream in) throws IOException {
    in = new BufferedInputStream(in);
    int avail = in.available();
    byte[] data = new byte[avail];
    int numRead = 0;
    int pos = 0;
    do {
      if (pos + avail > data.length) {
        byte[] newData = new byte[pos + avail];
        System.arraycopy(data, 0, newData, 0, pos);
        data = newData;
      }
      numRead = in.read(data, pos, avail);
      if (numRead >= 0) {
        pos += numRead;
      }
      avail = in.available();
    } while (avail > 0 && numRead >= 0);
    ByteBuffer buf = ByteBuffer.allocateDirect(pos);
    buf.put(data, 0, pos);
    buf.rewind();
    return loadMD2File(buf);
  }

  private static FileModel loadMD2File(String filename) throws IOException {
    FileInputStream fis = new FileInputStream(filename);
    FileChannel chan = fis.getChannel();
    ByteBuffer buf = chan.map(FileChannel.MapMode.READ_ONLY, 0, fis.available());
    FileModel md2p = loadMD2File(buf);
    chan.close();
    fis.close();
    return md2p;
  }

  private static FileHeader readHeader(ByteBuffer buf) {
    FileHeader header = new FileHeader();
    header.ident      = buf.getInt();
    header.version    = buf.getInt();
    header.skinwidth  = buf.getInt();
    header.skinheight = buf.getInt();
    header.framesize  = buf.getInt();
    header.num_skins  = buf.getInt();
    header.num_xyz    = buf.getInt();
    header.num_st     = buf.getInt();
    header.num_tris   = buf.getInt();
    header.num_glcmds = buf.getInt();
    header.num_frames = buf.getInt();
    header.ofs_skins  = buf.getInt();
    header.ofs_st     = buf.getInt();
    header.ofs_tris   = buf.getInt();
    header.ofs_frames = buf.getInt();
    header.ofs_glcmds = buf.getInt();
    header.ofs_end    = buf.getInt();
    return header;
  }

  private static int numVerts(int framesize) {
    return (framesize >> 2) - 10;
  }

  private static void readFrames(ByteBuffer buf, FileHeader header, FileModel md2p) throws IOException {
    int numframes = header.num_frames;
    int framesize = header.framesize;
    int numVerts = numVerts(framesize);
    FileFrame[] frames = new FileFrame[numframes];
    byte[] name = new byte[16];
    for (int i = 0; i < numframes; i++) {
      FileFrame frame = new FileFrame();
      frame.scale[0] = buf.getFloat();
      frame.scale[1] = buf.getFloat();
      frame.scale[2] = buf.getFloat();
      frame.translate[0] = buf.getFloat();
      frame.translate[1] = buf.getFloat();
      frame.translate[2] = buf.getFloat();
      buf.get(name);
      try {
        frame.name = new String(name, "US-ASCII");
      } catch (UnsupportedEncodingException e) {
        throw new IOException(e.toString());
      }
      frame.verts = new FileCompressedVertex[numVerts];
      for (int j = 0; j < numVerts; j++) {
        FileCompressedVertex vert = new FileCompressedVertex();
        buf.get(vert.v);
        vert.lightnormalindex = buf.get();
        frame.verts[j] = vert;
      }
      frames[i] = frame;
    }
    md2p.frames = frames;
  }

  private static void readGLCommands(ByteBuffer buf, FileHeader header, FileModel md2p) {
    int num_glcmds = header.num_glcmds;
    int[] glcmds = new int[num_glcmds];
    for (int i = 0; i < num_glcmds; i++) {
      glcmds[i] = buf.getInt();
    }
    md2p.glcmds = glcmds;
  }

  private static Vertex extractVertex(int[] glcmds, int[] idx) {
    Vertex v = new Vertex();
    v.tc.s = Float.intBitsToFloat(glcmds[idx[0]++]);
    v.tc.t = Float.intBitsToFloat(glcmds[idx[0]++]);
    v.pn_index = glcmds[idx[0]++];
    return v;
  }

  private static boolean compareFrames(List/*<IFrame>*/ m) {
    IFrame f0 = (IFrame) m.get(0);
    boolean same_topology  = true;
    boolean same_texcoords = true;

    for (int i = 1; i < m.size(); i++) {
      IFrame f = (IFrame) m.get(i);
      if (f.pn.length != f0.pn.length) {
        System.err.println("pn size different for iframe " + i + " :  " + f0.pn.length + " != " + f.pn.length);
        same_topology = false;
      }
      if (f.tri.length != f0.tri.length) {
        System.err.println("tri size different for iframe " + i + " :  " + f0.tri.length + " != " + f.tri.length);
        same_topology = false;
      }
      if (same_topology) {
        for (int j = 0; j < f.tri.length; j++) {
          Triangle t0 = f0.tri[j];
          Triangle t  = f.tri[j];
          for (int k = 0; k < 3; k++) {
            if (t0.v[k].pn_index != t.v[k].pn_index) {
              System.err.println("tri " + j + " triangle pn_index " + k + " different!");
              same_topology = false;
            }
            if (t0.v[k].tc.s != t.v[k].tc.s || t0.v[k].tc.t != t.v[k].tc.t) {
              System.err.println("tri " + j + " triangle tc " + k + " different!");
              same_texcoords = false;
            }
          }
        }
      }
    }

    return same_topology && same_texcoords;
  }

  /**
     Computes the plane equations for each polygon of a frame.
  */
  private static void computeFramePlanes(Triangle[] tri, Frame f) {
    f.triplane = new Plane[tri.length];
    for (int i = 0; i < tri.length; i++) {
      Triangle t = tri[i];
      int ia = t.v[0].pn_index;
      int ib = t.v[1].pn_index;
      int ic = t.v[2].pn_index;
      Plane p = new Plane();
      computePlane(f.pn[ia], f.pn[ib], f.pn[ic], p);
      f.triplane[i] = p;
    }
  }

  private static int computeWingedEdges(Model m) {
    Triangle[] tri = m.tri;
    List/*<WingedEdge>*/ edge = new ArrayList/*<WingedEdge>*/();

    // for each triangle, try to add each edge to the winged_edge vector,
    // but check first to see if it's already there
    int tsize = tri.length;
    for (int i = 0; i < tsize; i++) {
      Triangle t = tri[i];
      for (int j = 0; j < 3; j++) {
        WingedEdge we = new WingedEdge();
        we.e[0] = t.v[   j   ].pn_index;
        we.e[1] = t.v[(j+1)%3].pn_index;
        we.w[0] = i;
        we.w[1] = -1;  // subsequent attempt to add this edge will replace w[1]
        addEdge(edge, we);
      }
    }
    int open_edge = 0;
    for (int i = 0; i < edge.size(); i++) {
      if (((WingedEdge) edge.get(i)).w[1] == -1)
        open_edge++;
    }
    //fprintf(stderr, "out of % edges, there were %d open edges\n", edge.size(), open_edge);
    m.edge = (WingedEdge[]) edge.toArray(new WingedEdge[0]);
    return open_edge;
  }

  /**
     add_edge will look to see if the current edge is already in the list.
     If it is not, it will add it. If it is, it will replace the w[1] in
     the existing table with w[0] from the edge being added.
  */
  private static void addEdge(List/*<WingedEdge>*/ edge, WingedEdge we) {
    int esize = edge.size();
    for (int i=0; i < esize; i++) {
      WingedEdge we0 = (WingedEdge) edge.get(i);
      if (we0.e[0] == we.e[0] && we0.e[1] == we.e[1]) {
        System.err.println("facingness different between polys on edge!");
      }
      if(we0.e[0] == we.e[1]  && we0.e[1] == we.e[0]) {
        if(we0.w[1] != -1) {
          System.err.println("triple edge! bad...");
        }
        we0.w[1] = we.w[0]; // pair the edge and return
        return;
      }
    }
    edge.add(we);  // otherwise, add the new edge
  }

  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      try {
        MD2.Model model = loadMD2(args[i]);
        System.err.println("Successfully parsed " + args[i]);
      } catch (IOException e) {
        System.err.println("Error parsing " + args[i] + ":");
        e.printStackTrace();
      }
    }
  }
}

class ObjReader.java

package demos.util;

import java.io.*;
import java.nio.*;
import java.util.*;

import com.sun.opengl.util.*;

/** Simple parser for Wavefront .OBJ files. Does not support all file
    options -- currently requires vertices and normals (only) to be
    present. */

public class ObjReader {
  private int verticesPerFace = -1;
  private FloatBuffer vertices;
  private FloatBuffer normals;
  private float[] aabbMin = new float[3];
  private float[] aabbMax = new float[3];
  private float[] center = new float[3];
  private float radius;
  // If we wanted this to be really general we'd have an array of
  // FloatLists for the various kinds of vertices as well
  private FloatList tmpVertices;
  private FloatList tmpVertexNormals;
  private IntList   faceIndices;
  private IntList[] tmpFaceIndices;

  public ObjReader(String filename) throws IOException {
    this(new File(filename));
  }

  public ObjReader(InputStream in) throws IOException {
    this(new InputStreamReader(in));
  }

  public ObjReader(File file) throws IOException {
    this (new FileReader(file));
  }

  public ObjReader(Reader r) throws IOException {
    BufferedReader reader = new BufferedReader(r);
    String line = null;
    int lineNo = 0;
    float[] floatTmp = new float[3];

    while ((line = reader.readLine()) != null) {
      ++lineNo;
      if (line.length() > 0) {
        char c = line.charAt(0);
        // FIXME: support continuation of lines with trailing '\'
        switch (c) {
          case '#':
            break;

          case 'v':
            if (Character.isWhitespace(line.charAt(1))) {
              addVertex(parseFloats(line, 3, floatTmp, lineNo));
            } else if (line.startsWith("vn")) {
              addVertexNormal(parseFloats(line, 3, floatTmp, lineNo));
            } else {
              throw new IOException("Unsupported vertex command on line " + lineNo);
            }
            break;

          case 'f':
            parseIndices(line, lineNo);

          default:
            // For now we ignore all other lines
        }
      }
    }

    // Now have all vertex information.
    // Make it possible to use same indices for both vertices and normals
    condenseIndices();

    // Compute axis-aligned bounding box and radius
    computeBoundingBox();
  }

  public void rescale(float amount) {
    for (int i = 0; i < vertices.capacity(); i++) {
      vertices.put(i, vertices.get(i) * amount);
    }
  }

  public FloatBuffer getVertices() {
    return vertices;
  }

  public FloatBuffer getVertexNormals() {
    return normals;
  }

  public int[] getFaceIndices() {
    return faceIndices.getData();
  }

  public int getVerticesPerFace() {
    return verticesPerFace;
  }

  public float[] getAABBMin() {
    return aabbMin;
  }

  public float[] getAABBMax() {
    return aabbMax;
  }

  public float[] getCenter() {
    return center;
  }

  public float getRadius() {
    return radius;
  }

  //----------------------------------------------------------------------
  // Internals only below this point
  //

  private void addVertex(float[] tmp) {
    if (tmpVertices == null) {
      tmpVertices = new FloatList();
    }
    for (int i = 0; i < 3; i++) {
      tmpVertices.add(tmp[i]);
    }
  }

  private void addVertexNormal(float[] tmp) {
    if (tmpVertexNormals == null) {
      tmpVertexNormals = new FloatList();
    }
    for (int i = 0; i < 3; i++) {
      tmpVertexNormals.add(tmp[i]);
    }
  }

  private float[] parseFloats(String line, int num, float[] tmp, int lineNo) throws IOException {
    StringTokenizer tok = new StringTokenizer(line);
    tok.nextToken(); // skip command
    int idx = 0;
    while (tok.hasMoreTokens()) {
      if (idx >= tmp.length) {
        throw new IOException("Too many floating-point values on line " + lineNo);
      }
      tmp[idx++] = Float.parseFloat(tok.nextToken());
    }
    return tmp;
  }

  private void parseIndices(String line, int lineNo) throws IOException {
    StringTokenizer tok = new StringTokenizer(line);
    tok.nextToken(); // skip command
    List tokens = new ArrayList();
    while (tok.hasMoreTokens()) {
      tokens.add(tok.nextToken());
    }
    // This is the number of vertices in this face.
    // If we seem to have already found this, it had better match the
    // previously read value (for now - don't want to add the
    // complexity of supporting some faces with a certain number of
    // vertices and some with a different number)
    if (verticesPerFace < 0) {
      verticesPerFace = tokens.size();
    } else {
      if (verticesPerFace != tokens.size()) {
        throw new IOException("Face on line " + lineNo + " had " + tokens.size() +
                              " vertices, but had already previously set the number of vertices per face to " +
                              verticesPerFace);
      }
    }
    // Now read the individual indices out of each token
    for (Iterator iter = tokens.iterator(); iter.hasNext(); ) {
      String indices = (String) iter.next();
      if (tmpFaceIndices == null) {
        StringTokenizer tmpTok = new StringTokenizer(indices, "/");
        int numIndicesPerVertex = 0;
        while (tmpTok.hasMoreTokens()) {
          tmpTok.nextToken();
          ++numIndicesPerVertex;
        }
        tmpFaceIndices = new IntList[numIndicesPerVertex];
        for (int i = 0; i < numIndicesPerVertex; i++) {
          tmpFaceIndices[i] = new IntList();
        }
      }

      StringTokenizer tok2 = new StringTokenizer(indices, "/");
      int which = 0;
      while (tok2.hasMoreTokens()) {
        if (which >= tmpFaceIndices.length) {
          throw new IOException("Expected all vertices to have " + tmpFaceIndices.length +
                                " indices based on earlier input, but saw vertex with more on line " + lineNo);
        }
        String token = tok2.nextToken();
        int index = Integer.parseInt(token);
        tmpFaceIndices[which].add(index);
        ++which;
      }
    }
  }

  // Don't know the hashing rules for arrays off the top of my head
  static class Indices {
    int[] data;
    Indices(int[] data) {
      this.data = data;
    }

    public boolean equals(Object obj) {
      if ((obj == null) || (!(obj instanceof Indices))) {
        return false;
      }

      Indices other = (Indices) obj;

      if (data.length != other.data.length) {
        return false;
      }

      for (int i = 0; i < data.length; i++) {
        if (data[i] != other.data[i]) {
          return false;
        }
      }

      return true;
    }

    public int hashCode() {
      int hash = 0;
      for (int i = 0; i < data.length; i++) {
        hash ^= data[i];
      }
      return hash;
    }
  }

  private void condenseIndices() {
    FloatList newVertices = new FloatList();
    FloatList newVertexNormals = new FloatList();
    IntList   newIndices = new IntList();
    int nextIndex = 0;
    HashMap condensingMap = new HashMap();
    for (int i = 0; i < tmpFaceIndices[0].size(); i++) {
      Indices indices = getIndices(i);
      Integer newIndex = (Integer) condensingMap.get(indices);
      if (newIndex == null) {
        // Fabricate new vertex and normal index for this one
        // FIXME: generalize this by putting vertices and vertex
        // normals in FloatList[] as well
        condensingMap.put(indices, new Integer(nextIndex));
        int vtxIdx    = 3 * (indices.data[0] - 1);
        int vtxNrmIdx = 3 * (indices.data[1] - 1);
        newVertices.add(tmpVertices.get(vtxIdx + 0));
        newVertices.add(tmpVertices.get(vtxIdx + 1));
        newVertices.add(tmpVertices.get(vtxIdx + 2));
        newVertexNormals.add(tmpVertexNormals.get(vtxNrmIdx + 0));
        newVertexNormals.add(tmpVertexNormals.get(vtxNrmIdx + 1));
        newVertexNormals.add(tmpVertexNormals.get(vtxNrmIdx + 2));
        newIndices.add(nextIndex);
        ++nextIndex;
      } else {
        newIndices.add(newIndex.intValue());
      }
    }
    newVertices.trim();
    newVertexNormals.trim();
    newIndices.trim();
    vertices = BufferUtil.newFloatBuffer(newVertices.size());
    vertices.put(newVertices.getData());
    vertices.rewind();
    normals = BufferUtil.newFloatBuffer(newVertexNormals.size());
    normals.put(newVertexNormals.getData());
    normals.rewind();
    faceIndices = newIndices;
    tmpVertices = null;
    tmpVertexNormals = null;
  }

  private void computeBoundingBox() {
    for (int i = 0; i < vertices.capacity(); i += 3) {
      if (i == 0) {
        aabbMin[0] = vertices.get(i + 0);
        aabbMin[1] = vertices.get(i + 1);
        aabbMin[2] = vertices.get(i + 2);
        aabbMax[0] = vertices.get(i + 0);
        aabbMax[1] = vertices.get(i + 1);
        aabbMax[2] = vertices.get(i + 2);
      } else {
        aabbMin[0] = Math.min(aabbMin[0], vertices.get(i + 0));
        aabbMin[1] = Math.min(aabbMin[1], vertices.get(i + 1));
        aabbMin[2] = Math.min(aabbMin[2], vertices.get(i + 2));
        aabbMax[0] = Math.max(aabbMax[0], vertices.get(i + 0));
        aabbMax[1] = Math.max(aabbMax[1], vertices.get(i + 1));
        aabbMax[2] = Math.max(aabbMax[2], vertices.get(i + 2));
      }
    }
    center[0] = 0.5f * (aabbMin[0] + aabbMax[0]);
    center[1] = 0.5f * (aabbMin[1] + aabbMax[1]);
    center[2] = 0.5f * (aabbMin[2] + aabbMax[2]);
    radius = (float) Math.sqrt((aabbMax[0] - center[0]) * (aabbMax[0] - center[0]) +
                               (aabbMax[1] - center[1]) * (aabbMax[1] - center[1]) +
                               (aabbMax[2] - center[2]) * (aabbMax[2] - center[2]));
  }

  private Indices getIndices(int index) {
    int[] indices = new int[tmpFaceIndices.length];
    for (int i = 0; i < tmpFaceIndices.length; i++) {
      indices[i] = tmpFaceIndices[i].get(index);
    }
    return new Indices(indices);
  }
}

class ScreenResSelector.java

package demos.util;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

public class ScreenResSelector {
  static interface DisplayModeFilter {
    public boolean filter(DisplayMode mode);
  }

  public static java.util.List getAvailableDisplayModes() {
    java.util.List modes = getDisplayModes();
    final DisplayMode curMode = getCurrentDisplayMode();
    // Filter everything which is higher frequency than the current
    // display mode
    modes = filterDisplayModes(modes, new DisplayModeFilter() {
        public boolean filter(DisplayMode mode) {
          return (mode.getRefreshRate() <= curMode.getRefreshRate());
        }
      });
    // Filter everything that is not at least 24-bit
    modes = filterDisplayModes(modes, new DisplayModeFilter() {
        public boolean filter(DisplayMode mode) {
          // Bit depth < 0 means "multi-depth" -- can't reason about it
          return (mode.getBitDepth() < 0 || mode.getBitDepth() >= 24);
        }
      });
    // Filter everything less than 640x480
    modes = filterDisplayModes(modes, new DisplayModeFilter() {
        public boolean filter(DisplayMode mode) {
          return (mode.getWidth() >= 640 && mode.getHeight() >= 480);
        }
      });
    if (modes.size() == 0) {
      throw new RuntimeException("Couldn't find any valid display modes");
    }
    return modes;
  }

  /** Shows a modal dialog containing the available screen resolutions
      and requests selection of one of them. Returns the selected one. */
  public static DisplayMode showSelectionDialog() {
    SelectionDialog dialog = new SelectionDialog();
    dialog.setVisible(true);
    dialog.waitFor();
    return dialog.selected();
  }

  public static void main(String[] args) {
    DisplayMode mode = showSelectionDialog();
    if (mode != null) {
      System.err.println("Selected display mode:");
      System.err.println(modeToString(mode));
    } else {
      System.err.println("No display mode selected.");
    }
    System.exit(0);
  }

  //----------------------------------------------------------------------
  // Internals only below this point
  //

  private static DisplayMode getCurrentDisplayMode() {
    GraphicsDevice dev = getDefaultScreenDevice();
    return dev.getDisplayMode();
  }

  private static java.util.List/*<DisplayMode>*/ getDisplayModes() {
    GraphicsDevice dev = getDefaultScreenDevice();
    DisplayMode[] modes = dev.getDisplayModes();
    return toList(modes);
  }

  private static GraphicsDevice getDefaultScreenDevice() {
    return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
  }

  private static java.util.List/*<DisplayMode>*/ toList(DisplayMode[] modes) {
    java.util.List res = new ArrayList();
    for (int i = 0; i < modes.length; i++) {
      res.add(modes[i]);
    }
    return res;
  }

  private static String modeToString(DisplayMode mode) {
    return (((mode.getBitDepth() > 0) ? (mode.getBitDepth() + " bits, ") : "") +
            mode.getWidth() + "x" + mode.getHeight() + ", " +
            mode.getRefreshRate() + " Hz");
  }

  private static String[] modesToString(java.util.List/*<DisplayMode>*/ modes) {
    String[] res = new String[modes.size()];
    int i = 0;
    for (Iterator iter = modes.iterator(); iter.hasNext(); ) {
      res[i++] = modeToString((DisplayMode) iter.next());
    }
    return res;
  }

  private static java.util.List/*<DisplayMode>*/ filterDisplayModes(java.util.List/*<DisplayMode>*/ modes,
                                                                    DisplayModeFilter filter) {
    java.util.List res = new ArrayList();
    for (Iterator iter = modes.iterator(); iter.hasNext(); ) {
      DisplayMode mode = (DisplayMode) iter.next();
      if (filter.filter(mode)) {
        res.add(mode);
      }
    }
    return res;
  }

  static class SelectionDialog extends JFrame {
    private Object monitor = new Object();
    private java.util.List modes;
    private volatile boolean done = false;
    private volatile int selectedIndex;

    public SelectionDialog() {
      super();

      setTitle("Display Modes");
      modes = getAvailableDisplayModes();
      String[] strings = modesToString(modes);
      final JList modeList = new JList(strings);
      modeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      modeList.setSelectedIndex(0);
      JScrollPane scroller = new JScrollPane(modeList);
      getContentPane().setLayout(new BorderLayout());
      JPanel panel = new JPanel();
      panel.setLayout(new BorderLayout());
      panel.add(new JLabel("Select full-screen display mode,"), BorderLayout.NORTH);
      panel.add(new JLabel("or Cancel for windowed mode:"),     BorderLayout.CENTER);
      getContentPane().add(BorderLayout.NORTH, panel);
      getContentPane().add(BorderLayout.CENTER, scroller);
      JPanel buttonPanel = new JPanel();
      buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
      buttonPanel.add(Box.createGlue());
      JButton button = new JButton("OK");
      button.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            selectedIndex = modeList.getSelectedIndex();
            done = true;
            synchronized(monitor) {
              monitor.notify();
            }
            setVisible(false);
            dispose();
          }
        });
      buttonPanel.add(button);
      buttonPanel.add(Box.createHorizontalStrut(10));
      button = new JButton("Cancel");
      button.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            selectedIndex = -1;
            done = true;
            synchronized(monitor) {
              monitor.notify();
            }
            setVisible(false);
            dispose();
          }
        });
      buttonPanel.add(button);
      buttonPanel.add(Box.createGlue());
      getContentPane().add(BorderLayout.SOUTH, buttonPanel);
      setSize(300, 200);
      center(this, Toolkit.getDefaultToolkit().getScreenSize());
    }

    public void waitFor() {
      synchronized(monitor) {
        while (!done) {
          try {
            monitor.wait();
          } catch (InterruptedException e) {
          }
        }
      }
    }

    public DisplayMode selected() {
      if (selectedIndex < 0) {
        return null;
      } else {
        return (DisplayMode) modes.get(selectedIndex);
      }
    }
  }

  private static void center(Component component,
                             Dimension containerDimension) {
    Dimension sz = component.getSize();
    int x = ((containerDimension.width - sz.width) / 2);
    int y = ((containerDimension.height - sz.height) / 2);
    component.setLocation(x, y);
  }
}

class SystemTime.java

package demos.util;

/** Implementation of {@link demos.util.Time} interface based
    on {@link java.lang.System.currentTimeMillis}. Performs smoothing
    internally to avoid effects of poor granularity of
    currentTimeMillis on Windows platform in particular. */

public class SystemTime implements Time {
  private static final int DEFAULT_NUM_SMOOTHING_SAMPLES = 10;
  private long[] samples = new long[DEFAULT_NUM_SMOOTHING_SAMPLES];
  private int   numSmoothingSamples;
  private int   curSmoothingSample; // Index of current sample to be replaced
  private long baseTime = System.currentTimeMillis();
  private boolean hasCurTime;
  private double curTime;
  private double deltaT;

  /** Sets number of smoothing samples. Defaults to 10. Note that
      there may be a discontinuity in the reported time after a call
      to this method. */
  public void setNumSmoothingSamples(int num) {
    samples = new long[num];
    numSmoothingSamples = 0;
    curSmoothingSample = 0;
    hasCurTime = false;
  }

  /** Returns number of smoothing samples; default is 10. */
  public int getNumSmoothingSamples() {
    return samples.length;
  }

  /** Rebases this timer. After very long periods of time the
      resolution of this timer may decrease; the application can call
      this to restore higher resolution. Note that there may be a
      discontinuity in the reported time after a call to this
      method. */
  public void rebase() {
    baseTime = System.currentTimeMillis();
    setNumSmoothingSamples(samples.length);
  }

  public void update() {
    long tmpTime = System.currentTimeMillis();
    long diffSinceBase = tmpTime - baseTime;
    samples[curSmoothingSample] = diffSinceBase;
    curSmoothingSample = (curSmoothingSample + 1) % samples.length;
    numSmoothingSamples = Math.min(1 + numSmoothingSamples, samples.length);
    // Average of samples is current time
    double newCurTime = 0.0;
    for (int i = 0; i < numSmoothingSamples; i++) {
      newCurTime += samples[i];
    }
    newCurTime /= (1000.0f * numSmoothingSamples);
    double lastTime = curTime;
    if (!hasCurTime) {
      lastTime = newCurTime;
      hasCurTime = true;
    }
    deltaT = newCurTime - lastTime;
    curTime = newCurTime;
  }

  public double time() {
    return curTime;
  }

  public double deltaT() {
    return deltaT;
  }
}

class Time.java

[sourpackage demos.util;

/** Interface abstracting concept of time from applications. */

public interface Time {
/** Updates this Time object. Call update() each frame before
calling the accessor routines. */
public void update();
/** Time in seconds since beginning of application. */
public double time();
/** Time in seconds since last update. */
public double deltaT();
}
[/sourcecode]

class Triceratops.java

package demos.util;

import java.io.*;

import javax.media.opengl.*;

/** Renders a triceratops. <P>

	Copyright by Thomas Baier (thomas.baier@stmuc.com)<br>
	Created by OpenGL Export Plugin 1.0 at Fri Oct 27 22:04:55 2000<br>
	OpenGL-Structure <br><p>

        Ported to Java by Kenneth Russell
*/
public class Triceratops {

  /** Draws the triceratops object. Callers should capture the result
      in a display list. */
  public static void drawObject(GL gl) throws IOException {
    Reader reader = new BufferedReader(new InputStreamReader(
      Triceratops.class.getClassLoader().getResourceAsStream("demos/data/models/triceratops.txt")));
    StreamTokenizer tok = new StreamTokenizer(reader);
    // Reset tokenizer's syntax so numbers are not parsed
    tok.resetSyntax();
    tok.wordChars('a', 'z');
    tok.wordChars('A', 'Z');
    tok.wordChars('0', '9');
    tok.wordChars('-', '-');
    tok.wordChars('.', '.');
    tok.wordChars(128 + 32, 255);
    tok.whitespaceChars(0, ' ');
    tok.whitespaceChars(',', ',');
    tok.whitespaceChars('{', '{');
    tok.whitespaceChars('}', '}');
    tok.commentChar('/');
    tok.quoteChar('"');
    tok.quoteChar('\'');
    tok.slashSlashComments(true);
    tok.slashStarComments(true);

    // Read in file
    int numVertices = nextInt(tok, "number of vertices");
    float[] vertices = new float[numVertices * 3];
    for (int i = 0; i < numVertices * 3; i++) {
      vertices[i] = nextFloat(tok, "vertex");
    }
    int numNormals = nextInt(tok, "number of normals");
    float[] normals = new float[numNormals * 3];
    for (int i = 0; i < numNormals * 3; i++) {
      normals[i] = nextFloat(tok, "normal");
    }
    int numFaceIndices = nextInt(tok, "number of face indices");
    short[] faceIndices = new short[numFaceIndices * 9];
    for (int i = 0; i < numFaceIndices * 9; i++) {
      faceIndices[i] = (short) nextInt(tok, "face index");
    }

    reader.close();

    float sf = 0.1f;
    gl.glBegin(GL.GL_TRIANGLES);
    for (int i = 0; i < faceIndices.length; i += 9) {
      for (int j = 0; j < 3; j++) {
        int vi = faceIndices[i + j    ] & 0xFFFF;
        int ni = faceIndices[i + j + 3] & 0xFFFF;
        gl.glNormal3f(normals[3 * ni],
                      normals[3 * ni + 1],
                      normals[3 * ni + 2]);
        gl.glVertex3f(sf * vertices[3 * vi],
                      sf * vertices[3 * vi + 1],
                      sf * vertices[3 * vi + 2]);
      }
    }
    gl.glEnd();
  }

  private static int nextInt(StreamTokenizer tok, String error) throws IOException {
    if (tok.nextToken() != StreamTokenizer.TT_WORD) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
    try {
      return Integer.parseInt(tok.sval);
    } catch (NumberFormatException e) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
  }

  private static float nextFloat(StreamTokenizer tok, String error) throws IOException {
    if (tok.nextToken() != StreamTokenizer.TT_WORD) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
    try {
      return Float.parseFloat(tok.sval);
    } catch (NumberFormatException e) {
      throw new IOException("Parse error reading " + error + " at line " + tok.lineno());
    }
  }
}

class DimensionMismatchException.java

package gleem.linalg;

/** Thrown to indicate a mismatch of dimensionality of a matrix or
    vector. */

public class DimensionMismatchException extends RuntimeException {
  public DimensionMismatchException() {
    super();
  }

  public DimensionMismatchException(String msg) {
    super(msg);
  }
}

class IntersectionPoint.java

package gleem.linalg;

/** Wraps a 3D point and parametric time value. */

public class IntersectionPoint {
  private Vec3f intPt = new Vec3f();
  private float t;

  public Vec3f getIntersectionPoint() {
    return intPt;
  }

  public void setIntersectionPoint(Vec3f newPt) {
    intPt.set(newPt);
  }

  public float getT() {
    return t;
  }

  public void setT(float t) {
    this.t = t;
  }
}

class Line.java

xxxxxxx

class Mat2f.java

package gleem.linalg;

/** 2x2 matrix class useful for simple linear algebra. Representation
    is (as Mat4f) in row major order and assumes multiplication by
    column vectors on the right. */

public class Mat2f {
  private float[] data;

  /** Creates new matrix initialized to the zero matrix */
  public Mat2f() {
    data = new float[4];
  }

  /** Initialize to the identity matrix. */
  public void makeIdent() {
    for (int i = 0; i < 2; i++) {
      for (int j = 0; j < 2; j++) {
        if (i == j) {
          set(i, j, 1.0f);
        } else {
          set(i, j, 0.0f);
        }
      }
    }
  }

  /** Gets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public float get(int i, int j) {
    return data[2 * i + j];
  }

  /** Sets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public void set(int i, int j, float val) {
    data[2 * i + j] = val;
  }

  /** Set column i (i=[0..1]) to vector v. */
  public void setCol(int i, Vec2f v) {
    set(0, i, v.x());
    set(1, i, v.y());
  }

  /** Set row i (i=[0..1]) to vector v. */
  public void setRow(int i, Vec2f v) {
    set(i, 0, v.x());
    set(i, 1, v.y());
  }

  /** Transpose this matrix in place. */
  public void transpose() {
    float t = get(0, 1);
    set(0, 1, get(1, 0));
    set(1, 0, t);
  }

  /** Return the determinant. */
  public float determinant() {
    return (get(0, 0) * get(1, 1) - get(1, 0) * get(0, 1));
  }

  /** Full matrix inversion in place. If matrix is singular, returns
      false and matrix contents are untouched. If you know the matrix
      is orthonormal, you can call transpose() instead. */
  public boolean invert() {
    float det = determinant();
    if (det == 0.0f)
      return false;

    // Create transpose of cofactor matrix in place
    float t = get(0, 0);
    set(0, 0, get(1, 1));
    set(1, 1, t);
    set(0, 1, -get(0, 1));
    set(1, 0, -get(1, 0));

    // Now divide by determinant
    for (int i = 0; i < 4; i++) {
      data[i] /= det;
    }
    return true;
  }

  /** Multiply a 2D vector by this matrix. NOTE: src and dest must be
      different vectors. */
  public void xformVec(Vec2f src, Vec2f dest) {
    dest.set(get(0, 0) * src.x() +
             get(0, 1) * src.y(),

             get(1, 0) * src.x() +
             get(1, 1) * src.y());
  }

  /** Returns this * b; creates new matrix */
  public Mat2f mul(Mat2f b) {
    Mat2f tmp = new Mat2f();
    tmp.mul(this, b);
    return tmp;
  }

  /** this = a * b */
  public void mul(Mat2f a, Mat2f b) {
    for (int rc = 0; rc < 2; rc++)
      for (int cc = 0; cc < 2; cc++) {
        float tmp = 0.0f;
        for (int i = 0; i < 2; i++)
          tmp += a.get(rc, i) * b.get(i, cc);
        set(rc, cc, tmp);
      }
  }

  public Matf toMatf() {
    Matf out = new Matf(2, 2);
    for (int i = 0; i < 2; i++) {
      for (int j = 0; j < 2; j++) {
        out.set(i, j, get(i, j));
      }
    }
    return out;
  }

  public String toString() {
    String endl = System.getProperty("line.separator");
    return "(" +
      get(0, 0) + ", " + get(0, 1) + endl +
      get(1, 0) + ", " + get(1, 1) + ")";
  }
}

class Mat3f.java

package gleem.linalg;

/** 3x3 matrix class useful for simple linear algebra. Representation
    is (as Mat4f) in row major order and assumes multiplication by
    column vectors on the right. */

public class Mat3f {
  private float[] data;

  /** Creates new matrix initialized to the zero matrix */
  public Mat3f() {
    data = new float[9];
  }

  /** Initialize to the identity matrix. */
  public void makeIdent() {
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        if (i == j) {
          set(i, j, 1.0f);
        } else {
          set(i, j, 0.0f);
        }
      }
    }
  }

  /** Gets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public float get(int i, int j) {
    return data[3 * i + j];
  }

  /** Sets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public void set(int i, int j, float val) {
    data[3 * i + j] = val;
  }

  /** Set column i (i=[0..2]) to vector v. */
  public void setCol(int i, Vec3f v) {
    set(0, i, v.x());
    set(1, i, v.y());
    set(2, i, v.z());
  }

  /** Set row i (i=[0..2]) to vector v. */
  public void setRow(int i, Vec3f v) {
    set(i, 0, v.x());
    set(i, 1, v.y());
    set(i, 2, v.z());
  }

  /** Transpose this matrix in place. */
  public void transpose() {
    float t;
    t = get(0, 1);
    set(0, 1, get(1, 0));
    set(1, 0, t);

    t = get(0, 2);
    set(0, 2, get(2, 0));
    set(2, 0, t);

    t = get(1, 2);
    set(1, 2, get(2, 1));
    set(2, 1, t);
  }

  /** Return the determinant. Computed across the zeroth row. */
  public float determinant() {
    return (get(0, 0) * (get(1, 1) * get(2, 2) - get(2, 1) * get(1, 2)) +
            get(0, 1) * (get(2, 0) * get(1, 2) - get(1, 0) * get(2, 2)) +
            get(0, 2) * (get(1, 0) * get(2, 1) - get(2, 0) * get(1, 1)));
  }

  /** Full matrix inversion in place. If matrix is singular, returns
      false and matrix contents are untouched. If you know the matrix
      is orthonormal, you can call transpose() instead. */
  public boolean invert() {
    float det = determinant();
    if (det == 0.0f)
      return false;

    // Form cofactor matrix
    Mat3f cf = new Mat3f();
    cf.set(0, 0, get(1, 1) * get(2, 2) - get(2, 1) * get(1, 2));
    cf.set(0, 1, get(2, 0) * get(1, 2) - get(1, 0) * get(2, 2));
    cf.set(0, 2, get(1, 0) * get(2, 1) - get(2, 0) * get(1, 1));
    cf.set(1, 0, get(2, 1) * get(0, 2) - get(0, 1) * get(2, 2));
    cf.set(1, 1, get(0, 0) * get(2, 2) - get(2, 0) * get(0, 2));
    cf.set(1, 2, get(2, 0) * get(0, 1) - get(0, 0) * get(2, 1));
    cf.set(2, 0, get(0, 1) * get(1, 2) - get(1, 1) * get(0, 2));
    cf.set(2, 1, get(1, 0) * get(0, 2) - get(0, 0) * get(1, 2));
    cf.set(2, 2, get(0, 0) * get(1, 1) - get(1, 0) * get(0, 1));

    // Now copy back transposed
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
        set(i, j, cf.get(j, i) / det);
    return true;
  }

  /** Multiply a 3D vector by this matrix. NOTE: src and dest must be
      different vectors. */
  public void xformVec(Vec3f src, Vec3f dest) {
    dest.set(get(0, 0) * src.x() +
             get(0, 1) * src.y() +
             get(0, 2) * src.z(),

             get(1, 0) * src.x() +
             get(1, 1) * src.y() +
             get(1, 2) * src.z(),

             get(2, 0) * src.x() +
             get(2, 1) * src.y() +
             get(2, 2) * src.z());
  }

  /** Returns this * b; creates new matrix */
  public Mat3f mul(Mat3f b) {
    Mat3f tmp = new Mat3f();
    tmp.mul(this, b);
    return tmp;
  }

  /** this = a * b */
  public void mul(Mat3f a, Mat3f b) {
    for (int rc = 0; rc < 3; rc++)
      for (int cc = 0; cc < 3; cc++) {
        float tmp = 0.0f;
        for (int i = 0; i < 3; i++)
          tmp += a.get(rc, i) * b.get(i, cc);
        set(rc, cc, tmp);
      }
  }

  public Matf toMatf() {
    Matf out = new Matf(3, 3);
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        out.set(i, j, get(i, j));
      }
    }
    return out;
  }

  public String toString() {
    String endl = System.getProperty("line.separator");
    return "(" +
      get(0, 0) + ", " + get(0, 1) + ", " + get(0, 2) + endl +
      get(1, 0) + ", " + get(1, 1) + ", " + get(1, 2) + endl +
      get(2, 0) + ", " + get(2, 1) + ", " + get(2, 2) + ")";
  }
}

class Mat4f.java

package gleem.linalg;

/** A (very incomplete) 4x4 matrix class. Representation assumes
    multiplication by column vectors on the right. */

public class Mat4f {
  private float[] data;

  /** Creates new matrix initialized to the zero matrix */
  public Mat4f() {
    data = new float[16];
  }

  /** Creates new matrix initialized to argument's contents */
  public Mat4f(Mat4f arg) {
    this();
    set(arg);
  }

  /** Sets this matrix to the identity matrix */
  public void makeIdent() {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        if (i == j) {
          set(i, j, 1.0f);
        } else {
          set(i, j, 0.0f);
        }
      }
    }
  }

  /** Sets this matrix to be equivalent to the given one */
  public void set(Mat4f arg) {
    float[] mine = data;
    float[] yours = arg.data;
    for (int i = 0; i < mine.length; i++) {
      mine[i] = yours[i];
    }
  }

  /** Gets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public float get(int i, int j) {
    return data[4 * i + j];
  }

  /** Sets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public void set(int i, int j, float val) {
    data[4 * i + j] = val;
  }

  /** Sets the translation component of this matrix (i.e., the three
      top elements of the third column) without touching any of the
      other parts of the matrix */
  public void setTranslation(Vec3f trans) {
    set(0, 3, trans.x());
    set(1, 3, trans.y());
    set(2, 3, trans.z());
  }

  /** Sets the rotation component of this matrix (i.e., the upper left
      3x3) without touching any of the other parts of the matrix */
  public void setRotation(Rotf rot) {
    rot.toMatrix(this);
  }

  /** Sets the upper-left 3x3 of this matrix assuming that the given
      x, y, and z vectors form an orthonormal basis */
  public void setRotation(Vec3f x, Vec3f y, Vec3f z) {
    set(0, 0, x.x());
    set(1, 0, x.y());
    set(2, 0, x.z());

    set(0, 1, y.x());
    set(1, 1, y.y());
    set(2, 1, y.z());

    set(0, 2, z.x());
    set(1, 2, z.y());
    set(2, 2, z.z());
  }

  /** Gets the upper left 3x3 of this matrix as a rotation. Currently
      does not work if there are scales. Ignores translation
      component. */
  public void getRotation(Rotf rot) {
    rot.fromMatrix(this);
  }

  /** Sets the elements (0, 0), (1, 1), and (2, 2) with the
      appropriate elements of the given three-dimensional scale
      vector. Does not perform a full multiplication of the upper-left
      3x3; use this with an identity matrix in conjunction with
      <code>mul</code> for that. */
  public void setScale(Vec3f scale) {
    set(0, 0, scale.x());
    set(1, 1, scale.y());
    set(2, 2, scale.z());
  }

  /** Inverts this matrix assuming that it represents a rigid
      transform (i.e., some combination of rotations and
      translations). Assumes column vectors. Algorithm: transposes
      upper left 3x3; negates translation in rightmost column and
      transforms by inverted rotation. */
  public void invertRigid() {
    float t;
    // Transpose upper left 3x3
    t = get(0, 1);
    set(0, 1, get(1, 0));
    set(1, 0, t);
    t = get(0, 2);
    set(0, 2, get(2, 0));
    set(2, 0, t);
    t = get(1, 2);
    set(1, 2, get(2, 1));
    set(2, 1, t);
    // Transform negative translation by this
    Vec3f negTrans = new Vec3f(-get(0, 3), -get(1, 3), -get(2, 3));
    Vec3f trans = new Vec3f();
    xformDir(negTrans, trans);
    set(0, 3, trans.x());
    set(1, 3, trans.y());
    set(2, 3, trans.z());
  }

  /** Returns this * b; creates new matrix */
  public Mat4f mul(Mat4f b) {
    Mat4f tmp = new Mat4f();
    tmp.mul(this, b);
    return tmp;
  }

  /** this = a * b */
  public void mul(Mat4f a, Mat4f b) {
    for (int rc = 0; rc < 4; rc++)
      for (int cc = 0; cc < 4; cc++) {
        float tmp = 0.0f;
        for (int i = 0; i < 4; i++)
          tmp += a.get(rc, i) * b.get(i, cc);
        set(rc, cc, tmp);
      }
  }

  /** Transpose this matrix in place. */
  public void transpose() {
    float t;
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < i; j++) {
        t = get(i, j);
        set(i, j, get(j, i));
        set(j, i, t);
      }
    }
  }

  /** Multiply a 4D vector by this matrix. NOTE: src and dest must be
      different vectors. */
  public void xformVec(Vec4f src, Vec4f dest) {
    for (int rc = 0; rc < 4; rc++) {
      float tmp = 0.0f;
      for (int cc = 0; cc < 4; cc++) {
        tmp += get(rc, cc) * src.get(cc);
      }
      dest.set(rc, tmp);
    }
  }

  /** Transforms a 3D vector as though it had a homogeneous coordinate
      and assuming that this matrix represents only rigid
      transformations; i.e., is not a full transformation. NOTE: src
      and dest must be different vectors. */
  public void xformPt(Vec3f src, Vec3f dest) {
    for (int rc = 0; rc < 3; rc++) {
      float tmp = 0.0f;
      for (int cc = 0; cc < 3; cc++) {
        tmp += get(rc, cc) * src.get(cc);
      }
      tmp += get(rc, 3);
      dest.set(rc, tmp);
    }
  }

  /** Transforms src using only the upper left 3x3. NOTE: src and dest
      must be different vectors. */
  public void xformDir(Vec3f src, Vec3f dest) {
    for (int rc = 0; rc < 3; rc++) {
      float tmp = 0.0f;
      for (int cc = 0; cc < 3; cc++) {
        tmp += get(rc, cc) * src.get(cc);
      }
      dest.set(rc, tmp);
    }
  }

  /** Copies data in column-major (OpenGL format) order into passed
      float array, which must have length 16 or greater. */
  public void getColumnMajorData(float[] out) {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        out[4 * j + i] = get(i, j);
      }
    }
  }

  public Matf toMatf() {
    Matf out = new Matf(4, 4);
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        out.set(i, j, get(i, j));
      }
    }
    return out;
  }

  public String toString() {
    String endl = System.getProperty("line.separator");
    return "(" +
      get(0, 0) + ", " + get(0, 1) + ", " + get(0, 2) + ", " + get(0, 3) + endl +
      get(1, 0) + ", " + get(1, 1) + ", " + get(1, 2) + ", " + get(1, 3) + endl +
      get(2, 0) + ", " + get(2, 1) + ", " + get(2, 2) + ", " + get(2, 3) + endl +
      get(3, 0) + ", " + get(3, 1) + ", " + get(3, 2) + ", " + get(3, 3) + ")";
  }
}

class Matf.java

package gleem.linalg;

/** Arbitrary-size single-precision matrix class. Currently very
    simple and only supports a few needed operations. */

public class Matf {
  private float[] data;
  private int nCol; // number of columns
  private int nRow; // number of columns

  public Matf(int nRow, int nCol) {
    data = new float[nRow * nCol];
    this.nCol = nCol;
    this.nRow = nRow;
  }

  public Matf(Matf arg) {
    nRow = arg.nRow;
    nCol = arg.nCol;
    data = new float[nRow * nCol];
    System.arraycopy(arg.data, 0, data, 0, data.length);
  }

  public int nRow() {
    return nRow;
  }

  public int nCol() {
    return nCol;
  }

  /** Gets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public float get(int i, int j) {
    return data[nCol * i + j];
  }

  /** Sets the (i,j)th element of this matrix, where i is the row
      index and j is the column index */
  public void set(int i, int j, float val) {
    data[nCol * i + j] = val;
  }

  /** Returns transpose of this matrix; creates new matrix */
  public Matf transpose() {
    Matf tmp = new Matf(nCol, nRow);
    for (int i = 0; i < nRow; i++) {
      for (int j = 0; j < nCol; j++) {
        tmp.set(j, i, get(i, j));
      }
    }
    return tmp;
  }

  /** Returns this * b; creates new matrix */
  public Matf mul(Matf b) throws DimensionMismatchException {
    if (nCol() != b.nRow())
      throw new DimensionMismatchException();
    Matf tmp = new Matf(nRow(), b.nCol());
    for (int i = 0; i < nRow(); i++) {
      for (int j = 0; j < b.nCol(); j++) {
        float val = 0;
        for (int t = 0; t < nCol(); t++) {
          val += get(i, t) * b.get(t, j);
        }
        tmp.set(i, j, val);
      }
    }
    return tmp;
  }

  /** Returns this * v, assuming v is a column vector. */
  public Vecf mul(Vecf v) throws DimensionMismatchException {
    if (nCol() != v.length()) {
      throw new DimensionMismatchException();
    }
    Vecf out = new Vecf(nRow());
    for (int i = 0; i < nRow(); i++) {
      float tmp = 0;
      for (int j = 0; j < nCol(); j++) {
        tmp += get(i, j) * v.get(j);
      }
      out.set(i, tmp);
    }
    return out;
  }

  /** If this is a 2x2 matrix, returns it as a Mat2f. */
  public Mat2f toMat2f() throws DimensionMismatchException {
    if (nRow() != 2 || nCol() != 2) {
      throw new DimensionMismatchException();
    }
    Mat2f tmp = new Mat2f();
    for (int i = 0; i < 2; i++) {
      for (int j = 0; j < 2; j++) {
        tmp.set(i, j, get(i, j));
      }
    }
    return tmp;
  }

  /** If this is a 3x3 matrix, returns it as a Mat3f. */
  public Mat3f toMat3f() throws DimensionMismatchException {
    if (nRow() != 3 || nCol() != 3) {
      throw new DimensionMismatchException();
    }
    Mat3f tmp = new Mat3f();
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        tmp.set(i, j, get(i, j));
      }
    }
    return tmp;
  }

  /** If this is a 4x4 matrix, returns it as a Mat4f. */
  public Mat4f toMat4f() throws DimensionMismatchException {
    if (nRow() != 4 || nCol() != 4) {
      throw new DimensionMismatchException();
    }
    Mat4f tmp = new Mat4f();
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        tmp.set(i, j, get(i, j));
      }
    }
    return tmp;
  }
}

class MathUtil.java

package gleem.linalg;

/** Utility math routines. */

public class MathUtil {
  /** Makes an arbitrary vector perpendicular to <B>src</B> and
      inserts it into <B>dest</B>. Returns false if the source vector
      was equal to (0, 0, 0). */
  public static boolean makePerpendicular(Vec3f src,
                                          Vec3f dest) {
    if ((src.x() == 0.0f) && (src.y() == 0.0f) && (src.z() == 0.0f)) {
      return false;
    }

    if (src.x() != 0.0f) {
      if (src.y() != 0.0f) {
	dest.set(-src.y(), src.x(), 0.0f);
      }	else {
	dest.set(-src.z(), 0.0f, src.x());
      }
    } else {
      dest.set(1.0f, 0.0f, 0.0f);
    }
    return true;
  }

  /** Returns 1 if the sign of the given argument is positive; -1 if
      negative; 0 if 0. */
  public static int sgn(float f) {
    if (f > 0) {
      return 1;
    } else if (f < 0) {
      return -1;
    }
    return 0;
  }

  /** Clamps argument between min and max values. */
  public static float clamp(float val, float min, float max) {
    if (val < min) return min;
    if (val > max) return max;
    return val;
  }

  /** Clamps argument between min and max values. */
  public static int clamp(int val, int min, int max) {
    if (val < min) return min;
    if (val > max) return max;
    return val;
  }
}

class NonSquareMatrixException.java


package gleem.linalg;

/** Thrown to indicate a non-square matrix during an operation
    requiring one. */

public class NonSquareMatrixException extends RuntimeException {
  public NonSquareMatrixException() {
    super();
  }

  public NonSquareMatrixException(String msg) {
    super(msg);
  }
}

class Plane.java

package gleem.linalg;

/** Represents a plane in 3D space. */

public class Plane {
  /** Normalized */
  private Vec3f normal;
  private Vec3f point;
  /** Constant for faster projection and intersection */
  float c;

  /** Default constructor initializes normal to (0, 1, 0) and point to
      (0, 0, 0) */
  public Plane() {
    normal = new Vec3f(0, 1, 0);
    point = new Vec3f(0, 0, 0);
    recalc();
  }

  /** Sets all parameters of plane. Plane has normal <b>normal</b> and
      goes through the point <b>point</b>. Normal does not need to be
      unit length but must not be the zero vector. */
  public Plane(Vec3f normal, Vec3f point) {
    this.normal = new Vec3f(normal);
    this.normal.normalize();
    this.point = new Vec3f(point);
    recalc();
  }

  /** Setter does some work to maintain internal caches. Normal does
      not need to be unit length but must not be the zero vector. */
  public void setNormal(Vec3f normal) {
    this.normal.set(normal);
    this.normal.normalize();
    recalc();
  }

  /** Normal is normalized internally, so <b>normal</b> is not
      necessarily equal to <code>plane.setNormal(normal);
      plane.getNormal();</code> */
  public Vec3f getNormal() {
    return normal;
  }

  /** Setter does some work to maintain internal caches */
  public void setPoint(Vec3f point) {
    this.point.set(point);
    recalc();
  }

  public Vec3f getPoint() {
    return point;
  }

  /** Project a point onto the plane */
  public void projectPoint(Vec3f pt,
                           Vec3f projPt) {
    float scale = normal.dot(pt) - c;
    projPt.set(pt.minus(normal.times(normal.dot(point) - c)));
  }

  /** Intersect a ray with the plane. Returns true if intersection occurred, false
      otherwise. This is a two-sided ray cast. */
  public boolean intersectRay(Vec3f rayStart,
                              Vec3f rayDirection,
                              IntersectionPoint intPt) {
    float denom = normal.dot(rayDirection);
    if (denom == 0)
      return false;
    intPt.setT((c - normal.dot(rayStart)) / denom);
    intPt.setIntersectionPoint(rayStart.plus(rayDirection.times(intPt.getT())));
    return true;
  }

  //----------------------------------------------------------------------
  // Internals only below this point
  //

  private void recalc() {
    c = normal.dot(point);
  }
}

class PlaneUV.java

package gleem.linalg;

/** This differs from the Plane class in that it maintains an origin
    and orthonormal U, V axes in the plane so that it can project a 3D
    point to a 2D one. U cross V = normal. U and V coordinates are
    computed with respect to the origin. */

public class PlaneUV {
  private Vec3f origin = new Vec3f();
  /** Normalized */
  private Vec3f normal = new Vec3f();
  private Vec3f uAxis  = new Vec3f();
  private Vec3f vAxis  = new Vec3f();

  /** Default constructor initializes normal to (0, 1, 0), origin to
      (0, 0, 0), U axis to (1, 0, 0) and V axis to (0, 0, -1). */
  public PlaneUV() {
    setEverything(new Vec3f(0, 1, 0),
                  new Vec3f(0, 0, 0),
                  new Vec3f(1, 0, 0),
                  new Vec3f(0, 0, -1));
  }

  /** Takes normal vector and a point which the plane goes through
      (which becomes the plane's "origin"). Normal does NOT have to be
      normalized, but may not be zero vector. U and V axes are
      initialized to arbitrary values. */
  public PlaneUV(Vec3f normal, Vec3f origin) {
    setOrigin(origin);
    setNormal(normal);
  }

  /** Takes normal vector, point which plane goes through, and the "u"
    axis in the plane. Computes the "v" axis by taking the cross
    product of the normal and the u axis. Axis must be perpendicular
    to normal. Normal and uAxis do NOT have to be normalized, but
    neither may be the zero vector. */
  public PlaneUV(Vec3f normal,
                 Vec3f origin,
                 Vec3f uAxis) {
    setOrigin(origin);
    setNormalAndU(normal, uAxis);
  }

  /** Takes normal vector, point which plane goes through, and both
    the u and v axes. u axis cross v axis = normal. Normal, uAxis, and
    vAxis do NOT have to be normalized, but none may be the zero
    vector. */
  public PlaneUV(Vec3f normal,
                 Vec3f origin,
                 Vec3f uAxis,
                 Vec3f vAxis) {
    setEverything(normal, origin, uAxis, vAxis);
  }

  /** Set the origin, through which this plane goes and with respect
      to which U and V coordinates are computed */
  public void setOrigin(Vec3f origin) {
    this.origin.set(origin);
  }

  public Vec3f getOrigin() {
    return new Vec3f(origin);
  }

  /** Normal, U and V axes must be orthogonal and satisfy U cross V =
      normal, do not need to be unit length but must not be the zero
      vector. */
  public void setNormalAndUV(Vec3f normal,
                             Vec3f uAxis,
                             Vec3f vAxis) {
    setEverything(normal, origin, uAxis, vAxis);
  }

  /** This version sets the normal vector and generates new U and V
      axes. */
  public void setNormal(Vec3f normal) {
    Vec3f uAxis = new Vec3f();
    MathUtil.makePerpendicular(normal, uAxis);
    Vec3f vAxis = normal.cross(uAxis);
    setEverything(normal, origin, uAxis, vAxis);
  }

  /** This version computes the V axis from (normal cross U). */
  public void setNormalAndU(Vec3f normal,
                            Vec3f uAxis) {
    Vec3f vAxis = normal.cross(uAxis);
    setEverything(normal, origin, uAxis, vAxis);
  }

  /** Normal, U and V axes are normalized internally, so, for example,
      <b>normal</b> is not necessarily equal to
      <code>plane.setNormal(normal); plane.getNormal();</code> */
  public Vec3f getNormal() {
    return normal;
  }

  public Vec3f getUAxis() {
    return uAxis;
  }

  public Vec3f getVAxis() {
    return vAxis;
  }

  /** Project a point onto the plane */
  public void projectPoint(Vec3f point,
                           Vec3f projPt,
                           Vec2f uvCoords) {
    // Using projPt as a temporary
    projPt.sub(point, origin);
    float dotp = normal.dot(projPt);
    // Component perpendicular to plane
    Vec3f tmpDir = new Vec3f();
    tmpDir.set(normal);
    tmpDir.scale(dotp);
    projPt.sub(projPt, tmpDir);
    // Take dot products with basis vectors
    uvCoords.set(projPt.dot(uAxis),
                 projPt.dot(vAxis));
    // Add on center to intersection point
    projPt.add(origin);
  }

  /** Intersect a ray with this plane, outputting not only the 3D
      intersection point but also the U, V coordinates of the
      intersection. Returns true if intersection occurred, false
      otherwise. This is a two-sided ray cast. */
  public boolean intersectRay(Vec3f rayStart,
                              Vec3f rayDirection,
                              IntersectionPoint intPt,
                              Vec2f uvCoords) {
    float denom = rayDirection.dot(normal);
    if (denom == 0.0f)
      return false;
    Vec3f tmpDir = new Vec3f();
    tmpDir.sub(origin, rayStart);
    float t = tmpDir.dot(normal) / denom;
    // Find intersection point
    Vec3f tmpPt = new Vec3f();
    tmpPt.set(rayDirection);
    tmpPt.scale(t);
    tmpPt.add(rayStart);
    intPt.setIntersectionPoint(tmpPt);
    intPt.setT(t);
    // Find UV coords
    tmpDir.sub(intPt.getIntersectionPoint(), origin);
    uvCoords.set(tmpDir.dot(uAxis), tmpDir.dot(vAxis));
    return true;
  }

  private void setEverything(Vec3f normal,
                             Vec3f origin,
                             Vec3f uAxis,
                             Vec3f vAxis) {
    this.normal.set(normal);
    this.origin.set(origin);
    this.uAxis.set(uAxis);
    this.vAxis.set(vAxis);
    this.normal.normalize();
    this.uAxis.normalize();
    this.vAxis.normalize();
  }
}

class Rotf.java

package gleem.linalg;

/** Represents a rotation with single-precision components */

public class Rotf {
  private static float EPSILON = 1.0e-7f;

  // Representation is a quaternion. Element 0 is the scalar part (=
  // cos(theta/2)), elements 1..3 the imaginary/"vector" part (=
  // sin(theta/2) * axis).
  private float q0;
  private float q1;
  private float q2;
  private float q3;

  /** Default constructor initializes to the identity quaternion */
  public Rotf() {
    init();
  }

  public Rotf(Rotf arg) {
    set(arg);
  }

  /** Axis does not need to be normalized but must not be the zero
      vector. Angle is in radians. */
  public Rotf(Vec3f axis, float angle) {
    set(axis, angle);
  }

  /** Creates a rotation which will rotate vector "from" into vector
      "to". */
  public Rotf(Vec3f from, Vec3f to) {
    set(from, to);
  }

  /** Re-initialize this quaternion to be the identity quaternion "e"
      (i.e., no rotation) */
  public void init() {
    q0 = 1;
    q1 = q2 = q3 = 0;
  }

  /** Test for "approximate equality" -- performs componentwise test
      to see whether difference between all components is less than
      epsilon. */
  public boolean withinEpsilon(Rotf arg, float epsilon) {
    return ((Math.abs(q0 - arg.q0) < epsilon) &&
            (Math.abs(q1 - arg.q1) < epsilon) &&
            (Math.abs(q2 - arg.q2) < epsilon) &&
            (Math.abs(q3 - arg.q3) < epsilon));
  }

  /** Axis does not need to be normalized but must not be the zero
      vector. Angle is in radians. */
  public void set(Vec3f axis, float angle) {
    float halfTheta = angle / 2.0f;
    q0 = (float) Math.cos(halfTheta);
    float sinHalfTheta = (float) Math.sin(halfTheta);
    Vec3f realAxis = new Vec3f(axis);
    realAxis.normalize();
    q1 = realAxis.x() * sinHalfTheta;
    q2 = realAxis.y() * sinHalfTheta;
    q3 = realAxis.z() * sinHalfTheta;
  }

  public void set(Rotf arg) {
    q0 = arg.q0;
    q1 = arg.q1;
    q2 = arg.q2;
    q3 = arg.q3;
  }

  /** Sets this rotation to that which will rotate vector "from" into
      vector "to". from and to do not have to be the same length. */
  public void set(Vec3f from, Vec3f to) {
    Vec3f axis = from.cross(to);
    if (axis.lengthSquared() < EPSILON) {
      init();
      return;
    }
    float dotp = from.dot(to);
    float denom = from.length() * to.length();
    if (denom < EPSILON) {
      init();
      return;
    }
    dotp /= denom;
    set(axis, (float) Math.acos(dotp));
  }

  /** Returns angle (in radians) and mutates the given vector to be
      the axis. */
  public float get(Vec3f axis) {
    // FIXME: Is this numerically stable? Is there a better way to
    // extract the angle from a quaternion?
    // NOTE: remove (float) to illustrate compiler bug
    float retval = (float) (2.0f * Math.acos(q0));
    axis.set(q1, q2, q3);
    float len = axis.length();
    if (len == 0.0f) {
      axis.set(0, 0, 1);
    } else {
      axis.scale(1.0f / len);
    }
    return retval;
  }

  /** Returns inverse of this rotation; creates new rotation */
  public Rotf inverse() {
    Rotf tmp = new Rotf(this);
    tmp.invert();
    return tmp;
  }

  /** Mutate this quaternion to be its inverse. This is equivalent to
      the conjugate of the quaternion. */
  public void invert() {
    q1 = -q1;
    q2 = -q2;
    q3 = -q3;
  }

  /** Length of this quaternion in four-space */
  public float length() {
    return (float) Math.sqrt(lengthSquared());
  }

  /** This dotted with this */
  public float lengthSquared() {
    return (q0 * q0 +
            q1 * q1 +
            q2 * q2 +
            q3 * q3);
  }

  /** Make this quaternion a unit quaternion again. If you are
      composing dozens of quaternions you probably should call this
      periodically to ensure that you have a valid rotation. */
  public void normalize() {
    float len = length();
    q0 /= len;
    q1 /= len;
    q2 /= len;
    q3 /= len;
  }

  /** Returns this * b, in that order; creates new rotation */
  public Rotf times(Rotf b) {
    Rotf tmp = new Rotf();
    tmp.mul(this, b);
    return tmp;
  }

  /** Compose two rotations: this = A * B in that order. NOTE that
      because we assume a column vector representation that this
      implies that a vector rotated by the cumulative rotation will be
      rotated first by B, then A. NOTE: "this" must be different than
      both a and b. */
  public void mul(Rotf a, Rotf b) {
    q0 = (a.q0 * b.q0 - a.q1 * b.q1 -
          a.q2 * b.q2 - a.q3 * b.q3);
    q1 = (a.q0 * b.q1 + a.q1 * b.q0 +
          a.q2 * b.q3 - a.q3 * b.q2);
    q2 = (a.q0 * b.q2 + a.q2 * b.q0 -
          a.q1 * b.q3 + a.q3 * b.q1);
    q3 = (a.q0 * b.q3 + a.q3 * b.q0 +
          a.q1 * b.q2 - a.q2 * b.q1);
  }

  /** Turns this rotation into a 3x3 rotation matrix. NOTE: only
      mutates the upper-left 3x3 of the passed Mat4f. Implementation
      from B. K. P. Horn's <u>Robot Vision</u> textbook. */
  public void toMatrix(Mat4f mat) {
    float q00 = q0 * q0;
    float q11 = q1 * q1;
    float q22 = q2 * q2;
    float q33 = q3 * q3;
    // Diagonal elements
    mat.set(0, 0, q00 + q11 - q22 - q33);
    mat.set(1, 1, q00 - q11 + q22 - q33);
    mat.set(2, 2, q00 - q11 - q22 + q33);
    // 0,1 and 1,0 elements
    float q03 = q0 * q3;
    float q12 = q1 * q2;
    mat.set(0, 1, 2.0f * (q12 - q03));
    mat.set(1, 0, 2.0f * (q03 + q12));
    // 0,2 and 2,0 elements
    float q02 = q0 * q2;
    float q13 = q1 * q3;
    mat.set(0, 2, 2.0f * (q02 + q13));
    mat.set(2, 0, 2.0f * (q13 - q02));
    // 1,2 and 2,1 elements
    float q01 = q0 * q1;
    float q23 = q2 * q3;
    mat.set(1, 2, 2.0f * (q23 - q01));
    mat.set(2, 1, 2.0f * (q01 + q23));
  }

  /** Turns the upper left 3x3 of the passed matrix into a rotation.
      Implementation from Watt and Watt, <u>Advanced Animation and
      Rendering Techniques</u>.
      @see gleem.linalg.Mat4f#getRotation */
  public void fromMatrix(Mat4f mat) {
    // FIXME: Should reimplement to follow Horn's advice of using
    // eigenvector decomposition to handle roundoff error in given
    // matrix.

    float tr, s;
    int i, j, k;

    tr = mat.get(0, 0) + mat.get(1, 1) + mat.get(2, 2);
    if (tr > 0.0) {
      s = (float) Math.sqrt(tr + 1.0f);
      q0 = s * 0.5f;
      s = 0.5f / s;
      q1 = (mat.get(2, 1) - mat.get(1, 2)) * s;
      q2 = (mat.get(0, 2) - mat.get(2, 0)) * s;
      q3 = (mat.get(1, 0) - mat.get(0, 1)) * s;
    } else {
      i = 0;
      if (mat.get(1, 1) > mat.get(0, 0))
        i = 1;
      if (mat.get(2, 2) > mat.get(i, i))
        i = 2;
      j = (i+1)%3;
      k = (j+1)%3;
      s = (float) Math.sqrt( (mat.get(i, i) - (mat.get(j, j) + mat.get(k, k))) + 1.0f);
      setQ(i+1, s * 0.5f);
      s = 0.5f / s;
      q0 = (mat.get(k, j) - mat.get(j, k)) * s;
      setQ(j+1, (mat.get(j, i) + mat.get(i, j)) * s);
      setQ(k+1, (mat.get(k, i) + mat.get(i, k)) * s);
    }
  }

  /** Rotate a vector by this quaternion. Implementation is from
      Horn's <u>Robot Vision</u>. NOTE: src and dest must be different
      vectors. */
  public void rotateVector(Vec3f src, Vec3f dest) {
    Vec3f qVec = new Vec3f(q1, q2, q3);
    Vec3f qCrossX = qVec.cross(src);
    Vec3f qCrossXCrossQ = qCrossX.cross(qVec);
    qCrossX.scale(2.0f * q0);
    qCrossXCrossQ.scale(-2.0f);
    dest.add(src, qCrossX);
    dest.add(dest, qCrossXCrossQ);
  }

  /** Rotate a vector by this quaternion, returning newly-allocated result. */
  public Vec3f rotateVector(Vec3f src) {
    Vec3f tmp = new Vec3f();
    rotateVector(src, tmp);
    return tmp;
  }

  public String toString() {
    return "(" + q0 + ", " + q1 + ", " + q2 + ", " + q3 + ")";
  }

  private void setQ(int i, float val) {
    switch (i) {
    case 0: q0 = val; break;
    case 1: q1 = val; break;
    case 2: q2 = val; break;
    case 3: q3 = val; break;
    default: throw new IndexOutOfBoundsException();
    }
  }
}

class SingularMatrixException.java

package gleem.linalg;

/** Thrown to indicate a singular matrix during an inversion or
    related operation. */

public class SingularMatrixException extends RuntimeException {
  public SingularMatrixException() {
    super();
  }

  public SingularMatrixException(String msg) {
    super(msg);
  }
}

class Vec2f.java

package gleem.linalg;

/** 2-element single-precision vector */

public class Vec2f {
  private float x;
  private float y;

  public Vec2f() {}

  public Vec2f(Vec2f arg) {
    this(arg.x, arg.y);
  }

  public Vec2f(float x, float y) {
    set(x, y);
  }

  public Vec2f copy() {
    return new Vec2f(this);
  }

  public void set(Vec2f arg) {
    set(arg.x, arg.y);
  }

  public void set(float x, float y) {
    this.x = x;
    this.y = y;
  }

  /** Sets the ith component, 0 <= i < 2 */
  public void set(int i, float val) {
    switch (i) {
    case 0: x = val; break;
    case 1: y = val; break;
    default: throw new IndexOutOfBoundsException();
    }
  }

  /** Gets the ith component, 0 <= i < 2 */
  public float get(int i) {
    switch (i) {
    case 0: return x;
    case 1: return y;
    default: throw new IndexOutOfBoundsException();
    }
  }

  public float x() { return x; }
  public float y() { return y; }

  public void setX(float x) { this.x = x; }
  public void setY(float y) { this.y = y; }

  public float dot(Vec2f arg) {
    return x * arg.x + y * arg.y;
  }

  public float length() {
    return (float) Math.sqrt(lengthSquared());
  }

  public float lengthSquared() {
    return this.dot(this);
  }

  public void normalize() {
    float len = length();
    if (len == 0.0f) return;
    scale(1.0f / len);
  }

  /** Returns this * val; creates new vector */
  public Vec2f times(float val) {
    Vec2f tmp = new Vec2f(this);
    tmp.scale(val);
    return tmp;
  }

  /** this = this * val */
  public void scale(float val) {
    x *= val;
    y *= val;
  }

  /** Returns this + arg; creates new vector */
  public Vec2f plus(Vec2f arg) {
    Vec2f tmp = new Vec2f();
    tmp.add(this, arg);
    return tmp;
  }

  /** this = this + b */
  public void add(Vec2f b) {
    add(this, b);
  }

  /** this = a + b */
  public void add(Vec2f a, Vec2f b) {
    x = a.x + b.x;
    y = a.y + b.y;
  }

  /** Returns this + s * arg; creates new vector */
  public Vec2f addScaled(float s, Vec2f arg) {
    Vec2f tmp = new Vec2f();
    tmp.addScaled(this, s, arg);
    return tmp;
  }

  /** this = a + s * b */
  public void addScaled(Vec2f a, float s, Vec2f b) {
    x = a.x + s * b.x;
    y = a.y + s * b.y;
  }

  /** Returns this - arg; creates new vector */
  public Vec2f minus(Vec2f arg) {
    Vec2f tmp = new Vec2f();
    tmp.sub(this, arg);
    return tmp;
  }

  /** this = this - b */
  public void sub(Vec2f b) {
    sub(this, b);
  }

  /** this = a - b */
  public void sub(Vec2f a, Vec2f b) {
    x = a.x - b.x;
    y = a.y - b.y;
  }

  public Vecf toVecf() {
    Vecf out = new Vecf(2);
    for (int i = 0; i < 2; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public String toString() {
    return "(" + x + ", " + y + ")";
  }
}

class Vec3d.java

package gleem.linalg;

/** 3-element double-precision vector */

public class Vec3d {
  private double x;
  private double y;
  private double z;

  public Vec3d() {}

  public Vec3d(Vec3d arg) {
    set(arg);
  }

  public Vec3d(double x, double y, double z) {
    set(x, y, z);
  }

  public Vec3d copy() {
    return new Vec3d(this);
  }

  /** Convert to single-precision */
  public Vec3f toFloat() {
    return new Vec3f((float) x, (float) y, (float) z);
  }

  public void set(Vec3d arg) {
    set(arg.x, arg.y, arg.z);
  }

  public void set(double x, double y, double z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  /** Sets the ith component, 0 <= i < 3 */
  public void set(int i, double val) {
    switch (i) {
    case 0: x = val; break;
    case 1: y = val; break;
    case 2: z = val; break;
    default: throw new IndexOutOfBoundsException();
    }
  }

  /** Gets the ith component, 0 <= i < 3 */
  public double get(int i) {
    switch (i) {
    case 0: return x;
    case 1: return y;
    case 2: return z;
    default: throw new IndexOutOfBoundsException();
    }
  }

  public double x() { return x; }
  public double y() { return y; }
  public double z() { return z; }

  public void setX(double x) { this.x = x; }
  public void setY(double y) { this.y = y; }
  public void setZ(double z) { this.z = z; }

  public double dot(Vec3d arg) {
    return x * arg.x + y * arg.y + z * arg.z;
  }

  public double length() {
    return Math.sqrt(lengthSquared());
  }

  public double lengthSquared() {
    return this.dot(this);
  }

  public void normalize() {
    double len = length();
    if (len == 0.0) return;
    scale(1.0f / len);
  }

  /** Returns this * val; creates new vector */
  public Vec3d times(double val) {
    Vec3d tmp = new Vec3d(this);
    tmp.scale(val);
    return tmp;
  }

  /** this = this * val */
  public void scale(double val) {
    x *= val;
    y *= val;
    z *= val;
  }

  /** Returns this + arg; creates new vector */
  public Vec3d plus(Vec3d arg) {
    Vec3d tmp = new Vec3d();
    tmp.add(this, arg);
    return tmp;
  }

  /** this = this + b */
  public void add(Vec3d b) {
    add(this, b);
  }

  /** this = a + b */
  public void add(Vec3d a, Vec3d b) {
    x = a.x + b.x;
    y = a.y + b.y;
    z = a.z + b.z;
  }

  /** Returns this + s * arg; creates new vector */
  public Vec3d addScaled(double s, Vec3d arg) {
    Vec3d tmp = new Vec3d();
    tmp.addScaled(this, s, arg);
    return tmp;
  }

  /** this = a + s * b */
  public void addScaled(Vec3d a, double s, Vec3d b) {
    x = a.x + s * b.x;
    y = a.y + s * b.y;
    z = a.z + s * b.z;
  }

  /** Returns this - arg; creates new vector */
  public Vec3d minus(Vec3d arg) {
    Vec3d tmp = new Vec3d();
    tmp.sub(this, arg);
    return tmp;
  }

  /** this = this - b */
  public void sub(Vec3d b) {
    sub(this, b);
  }

  /** this = a - b */
  public void sub(Vec3d a, Vec3d b) {
    x = a.x - b.x;
    y = a.y - b.y;
    z = a.z - b.z;
  }

  /** Returns this cross arg; creates new vector */
  public Vec3d cross(Vec3d arg) {
    Vec3d tmp = new Vec3d();
    tmp.cross(this, arg);
    return tmp;
  }

  /** this = a cross b. NOTE: "this" must be a different vector than
      both a and b. */
  public void cross(Vec3d a, Vec3d b) {
    x = a.y * b.z - a.z * b.y;
    y = a.z * b.x - a.x * b.z;
    z = a.x * b.y - a.y * b.x;
  }

  public String toString() {
    return "(" + x + ", " + y + ", " + z + ")";
  }
}

class Vec3f.java

package gleem.linalg;

/** 3-element single-precision vector */

public class Vec3f {
  public static final Vec3f X_AXIS     = new Vec3f( 1,  0,  0);
  public static final Vec3f Y_AXIS     = new Vec3f( 0,  1,  0);
  public static final Vec3f Z_AXIS     = new Vec3f( 0,  0,  1);
  public static final Vec3f NEG_X_AXIS = new Vec3f(-1,  0,  0);
  public static final Vec3f NEG_Y_AXIS = new Vec3f( 0, -1,  0);
  public static final Vec3f NEG_Z_AXIS = new Vec3f( 0,  0, -1);

  private float x;
  private float y;
  private float z;

  public Vec3f() {}

  public Vec3f(Vec3f arg) {
    set(arg);
  }

  public Vec3f(float x, float y, float z) {
    set(x, y, z);
  }

  public Vec3f copy() {
    return new Vec3f(this);
  }

  /** Convert to double-precision */
  public Vec3d toDouble() {
    return new Vec3d(x, y, z);
  }

  public void set(Vec3f arg) {
    set(arg.x, arg.y, arg.z);
  }

  public void set(float x, float y, float z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  /** Sets the ith component, 0 <= i < 3 */
  public void set(int i, float val) {
    switch (i) {
    case 0: x = val; break;
    case 1: y = val; break;
    case 2: z = val; break;
    default: throw new IndexOutOfBoundsException();
    }
  }

  /** Gets the ith component, 0 <= i < 3 */
  public float get(int i) {
    switch (i) {
    case 0: return x;
    case 1: return y;
    case 2: return z;
    default: throw new IndexOutOfBoundsException();
    }
  }

  public float x() { return x; }
  public float y() { return y; }
  public float z() { return z; }

  public void setX(float x) { this.x = x; }
  public void setY(float y) { this.y = y; }
  public void setZ(float z) { this.z = z; }

  public float dot(Vec3f arg) {
    return x * arg.x + y * arg.y + z * arg.z;
  }

  public float length() {
    return (float) Math.sqrt(lengthSquared());
  }

  public float lengthSquared() {
    return this.dot(this);
  }

  public void normalize() {
    float len = length();
    if (len == 0.0f) return;
    scale(1.0f / len);
  }

  /** Returns this * val; creates new vector */
  public Vec3f times(float val) {
    Vec3f tmp = new Vec3f(this);
    tmp.scale(val);
    return tmp;
  }

  /** this = this * val */
  public void scale(float val) {
    x *= val;
    y *= val;
    z *= val;
  }

  /** Returns this + arg; creates new vector */
  public Vec3f plus(Vec3f arg) {
    Vec3f tmp = new Vec3f();
    tmp.add(this, arg);
    return tmp;
  }

  /** this = this + b */
  public void add(Vec3f b) {
    add(this, b);
  }

  /** this = a + b */
  public void add(Vec3f a, Vec3f b) {
    x = a.x + b.x;
    y = a.y + b.y;
    z = a.z + b.z;
  }

  /** Returns this + s * arg; creates new vector */
  public Vec3f addScaled(float s, Vec3f arg) {
    Vec3f tmp = new Vec3f();
    tmp.addScaled(this, s, arg);
    return tmp;
  }

  /** this = a + s * b */
  public void addScaled(Vec3f a, float s, Vec3f b) {
    x = a.x + s * b.x;
    y = a.y + s * b.y;
    z = a.z + s * b.z;
  }

  /** Returns this - arg; creates new vector */
  public Vec3f minus(Vec3f arg) {
    Vec3f tmp = new Vec3f();
    tmp.sub(this, arg);
    return tmp;
  }

  /** this = this - b */
  public void sub(Vec3f b) {
    sub(this, b);
  }

  /** this = a - b */
  public void sub(Vec3f a, Vec3f b) {
    x = a.x - b.x;
    y = a.y - b.y;
    z = a.z - b.z;
  }

  /** Returns this cross arg; creates new vector */
  public Vec3f cross(Vec3f arg) {
    Vec3f tmp = new Vec3f();
    tmp.cross(this, arg);
    return tmp;
  }

  /** this = a cross b. NOTE: "this" must be a different vector than
      both a and b. */
  public void cross(Vec3f a, Vec3f b) {
    x = a.y * b.z - a.z * b.y;
    y = a.z * b.x - a.x * b.z;
    z = a.x * b.y - a.y * b.x;
  }

  /** Sets each component of this vector to the product of the
      component with the corresponding component of the argument
      vector. */
  public void componentMul(Vec3f arg) {
    x *= arg.x;
    y *= arg.y;
    z *= arg.z;
  }

  public Vecf toVecf() {
    Vecf out = new Vecf(3);
    for (int i = 0; i < 3; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public String toString() {
    return "(" + x + ", " + y + ", " + z + ")";
  }
}

class Vec4f.java

package gleem.linalg;

/** 4-element single-precision vector */

public class Vec4f {
  private float x;
  private float y;
  private float z;
  private float w;

  public Vec4f() {}

  public Vec4f(Vec4f arg) {
    set(arg);
  }

  public Vec4f(float x, float y, float z, float w) {
    set(x, y, z, w);
  }

  public Vec4f copy() {
    return new Vec4f(this);
  }

  public void set(Vec4f arg) {
    set(arg.x, arg.y, arg.z, arg.w);
  }

  public void set(float x, float y, float z, float w) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.w = w;
  }

  /** Sets the ith component, 0 <= i < 4 */
  public void set(int i, float val) {
    switch (i) {
    case 0: x = val; break;
    case 1: y = val; break;
    case 2: z = val; break;
    case 3: w = val; break;
    default: throw new IndexOutOfBoundsException();
    }
  }

  /** Gets the ith component, 0 <= i < 4 */
  public float get(int i) {
    switch (i) {
    case 0: return x;
    case 1: return y;
    case 2: return z;
    case 3: return w;
    default: throw new IndexOutOfBoundsException();
    }
  }

  public float x() { return x; }
  public float y() { return y; }
  public float z() { return z; }
  public float w() { return w; }

  public void setX(float x) { this.x = x; }
  public void setY(float y) { this.y = y; }
  public void setZ(float z) { this.z = z; }
  public void setW(float w) { this.w = w; }

  public float dot(Vec4f arg) {
    return x * arg.x + y * arg.y + z * arg.z + w * arg.w;
  }

  public float length() {
    return (float) Math.sqrt(lengthSquared());
  }

  public float lengthSquared() {
    return this.dot(this);
  }

  public void normalize() {
    float len = length();
    if (len == 0.0f) return;
    scale(1.0f / len);
  }

  /** Returns this * val; creates new vector */
  public Vec4f times(float val) {
    Vec4f tmp = new Vec4f(this);
    tmp.scale(val);
    return tmp;
  }

  /** this = this * val */
  public void scale(float val) {
    x *= val;
    y *= val;
    z *= val;
    w *= val;
  }

  /** Returns this + arg; creates new vector */
  public Vec4f plus(Vec4f arg) {
    Vec4f tmp = new Vec4f();
    tmp.add(this, arg);
    return tmp;
  }

  /** this = this + b */
  public void add(Vec4f b) {
    add(this, b);
  }

  /** this = a + b */
  public void add(Vec4f a, Vec4f b) {
    x = a.x + b.x;
    y = a.y + b.y;
    z = a.z + b.z;
    w = a.w + b.w;
  }

  /** Returns this + s * arg; creates new vector */
  public Vec4f addScaled(float s, Vec4f arg) {
    Vec4f tmp = new Vec4f();
    tmp.addScaled(this, s, arg);
    return tmp;
  }

  /** this = a + s * b */
  public void addScaled(Vec4f a, float s, Vec4f b) {
    x = a.x + s * b.x;
    y = a.y + s * b.y;
    z = a.z + s * b.z;
    w = a.w + s * b.w;
  }

  /** Returns this - arg; creates new vector */
  public Vec4f minus(Vec4f arg) {
    Vec4f tmp = new Vec4f();
    tmp.sub(this, arg);
    return tmp;
  }

  /** this = this - b */
  public void sub(Vec4f b) {
    sub(this, b);
  }

  /** this = a - b */
  public void sub(Vec4f a, Vec4f b) {
    x = a.x - b.x;
    y = a.y - b.y;
    z = a.z - b.z;
    w = a.w - b.w;
  }

  /** Sets each component of this vector to the product of the
      component with the corresponding component of the argument
      vector. */
  public void componentMul(Vec4f arg) {
    x *= arg.x;
    y *= arg.y;
    z *= arg.z;
    w *= arg.w;
  }

  public Vecf toVecf() {
    Vecf out = new Vecf(4);
    for (int i = 0; i < 4; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public String toString() {
    return "(" + x + ", " + y + ", " + z + ")";
  }
}

class Vecf.java

package gleem.linalg;

/** Arbitrary-length single-precision vector class. Currently very
    simple and only supports a few needed operations. */

public class Vecf {
  private float[] data;

  public Vecf(int n) {
    data = new float[n];
  }

  public Vecf(Vecf arg) {
    data = new float[arg.data.length];
    System.arraycopy(arg.data, 0, data, 0, data.length);
  }

  public int length() {
    return data.length;
  }

  public float get(int i) {
    return data[i];
  }

  public void set(int i, float val) {
    data[i] = val;
  }

  public Vec2f toVec2f() throws DimensionMismatchException {
    if (length() != 2)
      throw new DimensionMismatchException();
    Vec2f out = new Vec2f();
    for (int i = 0; i < 2; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public Vec3f toVec3f() throws DimensionMismatchException {
    if (length() != 3)
      throw new DimensionMismatchException();
    Vec3f out = new Vec3f();
    for (int i = 0; i < 3; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public Veci toInt() {
    Veci out = new Veci(length());
    for (int i = 0; i < length(); i++) {
      out.set(i, (int) get(i));
    }
    return out;
  }
}

class Veci.java

package gleem.linalg;

/** Arbitrary-length integer vector class. Currently very simple and
    only supports a few needed operations. */

public class Veci {
  private int[] data;

  public Veci(int n) {
    data = new int[n];
  }

  public Veci(Veci arg) {
    data = new int[arg.data.length];
    System.arraycopy(arg.data, 0, data, 0, data.length);
  }

  public int length() {
    return data.length;
  }

  public int get(int i) {
    return data[i];
  }

  public void set(int i, int val) {
    data[i] = val;
  }

  public Vec2f toVec2f() throws DimensionMismatchException {
    if (length() != 2)
      throw new DimensionMismatchException();
    Vec2f out = new Vec2f();
    for (int i = 0; i < 2; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public Vec3f toVec3f() throws DimensionMismatchException {
    if (length() != 3)
      throw new DimensionMismatchException();
    Vec3f out = new Vec3f();
    for (int i = 0; i < 3; i++) {
      out.set(i, get(i));
    }
    return out;
  }

  public Vecf toVecf() {
    Vecf out = new Vecf(length());
    for (int i = 0; i < length(); i++) {
      out.set(i, get(i));
    }
    return out;
  }
}

Di bawah ini ada step by step screenshotnya untuk aplikasi openGL :

Kalian bisa download di sini :
Source code dan executable file (extract dulu)

Semoga Bermanfaat :D

6 responses to “Membuat aplikasi openGL dengan menggunakan java

  1. aldot 27 Oktober 2010 pukul 8:02 PM

    kerenz bro .
    tp kurang jelas .
    source itu dtaruh dmna.a ?
    beri kemudahan .
    thx :)

    • dimas347 29 Oktober 2010 pukul 6:02 AM

      itu tinggal di copy tiap classnya, terus lu save atas nama class masing2, kl masih gak bisa langsung download ajah filenya di paling bawah juga ada kok :D

  2. deeyan 14 April 2011 pukul 12:04 PM

    like thisss …

  3. xaxioza 20 September 2011 pukul 1:22 PM

    Makasih..Udah mau share ilmunya

  4. J1d1n 19 Oktober 2011 pukul 4:10 AM

    kereeeen…
    mkasih bro ilmunya

  5. candra wijaya 12 November 2011 pukul 10:59 PM

    bro…
    mau tanya nih…
    kalo mau enable button dari jinternalframe yang ada di jframe utama itu gmna caranya yah?
    ni contoh programnya gan… tolong yah…

    http://www.ziddu.com/download/14445937/Test1.rar.html

    jadi disitu ane masih bingung gmna caranya mengaktifkan tombol yg ada di frame utama dari jinternal frame…

    ni nomor hape ane kalo mau kontak gan…

    buat tugas kuliah nih,pusingg…. hahahaha 085878422339

Berikan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Logout / Ubah )

Twitter picture

You are commenting using your Twitter account. Logout / Ubah )

Facebook photo

You are commenting using your Facebook account. Logout / Ubah )

Google+ photo

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

Ikuti

Get every new post delivered to your Inbox.

Bergabunglah dengan 173 pengikut lainnya.

%d blogger menyukai ini: