ThreeD.java revision 4378
0N/A/*
3847N/A * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
0N/A *
0N/A * Redistribution and use in source and binary forms, with or without
0N/A * modification, are permitted provided that the following conditions
0N/A * are met:
0N/A *
0N/A * - Redistributions of source code must retain the above copyright
0N/A * notice, this list of conditions and the following disclaimer.
0N/A *
0N/A * - Redistributions in binary form must reproduce the above copyright
0N/A * notice, this list of conditions and the following disclaimer in the
0N/A * documentation and/or other materials provided with the distribution.
0N/A *
2362N/A * - Neither the name of Oracle nor the names of its
0N/A * contributors may be used to endorse or promote products derived
0N/A * from this software without specific prior written permission.
0N/A *
0N/A * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
0N/A * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
0N/A * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0N/A * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0N/A * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0N/A * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0N/A * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0N/A * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0N/A * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0N/A * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0N/A * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0N/A */
0N/A
4378N/A/*
4378N/A * This source code is provided to illustrate the usage of a given feature
4378N/A * or technique and has been deliberately simplified. Additional steps
4378N/A * required for a production-quality application, such as security checks,
4378N/A * input validation and proper error handling, might not be present in
4378N/A * this sample code.
4378N/A */
4378N/A
4378N/A
0N/A
0N/Aimport java.applet.Applet;
0N/Aimport java.awt.Graphics;
0N/Aimport java.awt.Color;
0N/Aimport java.awt.event.*;
0N/Aimport java.io.*;
0N/Aimport java.net.URL;
0N/A
3847N/A
3847N/A/* A set of classes to parse, represent and display 3D wireframe models
3847N/Arepresented in Wavefront .obj format. */
3847N/A@SuppressWarnings("serial")
0N/Aclass FileFormatException extends Exception {
3847N/A
0N/A public FileFormatException(String s) {
0N/A super(s);
0N/A }
0N/A}
0N/A
3847N/A
0N/A/** The representation of a 3D model */
3847N/Afinal class Model3D {
3847N/A
0N/A float vert[];
0N/A int tvert[];
0N/A int nvert, maxvert;
0N/A int con[];
0N/A int ncon, maxcon;
0N/A boolean transformed;
0N/A Matrix3D mat;
0N/A float xmin, xmax, ymin, ymax, zmin, zmax;
0N/A
3847N/A Model3D() {
3847N/A mat = new Matrix3D();
0N/A mat.xrot(20);
0N/A mat.yrot(30);
0N/A }
3847N/A
0N/A /** Create a 3D model by parsing an input stream */
3847N/A Model3D(InputStream is) throws IOException, FileFormatException {
3847N/A this();
3847N/A StreamTokenizer st = new StreamTokenizer(
3847N/A new BufferedReader(new InputStreamReader(is, "UTF-8")));
3847N/A st.eolIsSignificant(true);
3847N/A st.commentChar('#');
3847N/A scan:
0N/A while (true) {
0N/A switch (st.nextToken()) {
3847N/A default:
3847N/A break scan;
3847N/A case StreamTokenizer.TT_EOL:
3847N/A break;
3847N/A case StreamTokenizer.TT_WORD:
3847N/A if ("v".equals(st.sval)) {
3847N/A double x = 0, y = 0, z = 0;
0N/A if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
3847N/A x = st.nval;
3847N/A if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
3847N/A y = st.nval;
3847N/A if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
3847N/A z = st.nval;
3847N/A }
3847N/A }
3847N/A }
3847N/A addVert((float) x, (float) y, (float) z);
3847N/A while (st.ttype != StreamTokenizer.TT_EOL && st.ttype
3847N/A != StreamTokenizer.TT_EOF) {
3847N/A st.nextToken();
3847N/A }
3847N/A } else if ("f".equals(st.sval) || "fo".equals(st.sval) || "l".
3847N/A equals(st.sval)) {
3847N/A int start = -1;
3847N/A int prev = -1;
3847N/A int n = -1;
3847N/A while (true) {
3847N/A if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
3847N/A n = (int) st.nval;
3847N/A if (prev >= 0) {
3847N/A add(prev - 1, n - 1);
3847N/A }
3847N/A if (start < 0) {
3847N/A start = n;
3847N/A }
3847N/A prev = n;
3847N/A } else if (st.ttype == '/') {
3847N/A st.nextToken();
3847N/A } else {
3847N/A break;
3847N/A }
3847N/A }
3847N/A if (start >= 0) {
3847N/A add(start - 1, prev - 1);
3847N/A }
3847N/A if (st.ttype != StreamTokenizer.TT_EOL) {
3847N/A break scan;
3847N/A }
3847N/A } else {
3847N/A while (st.nextToken() != StreamTokenizer.TT_EOL
3847N/A && st.ttype != StreamTokenizer.TT_EOF) {
3847N/A // no-op
0N/A }
0N/A }
0N/A }
0N/A }
0N/A is.close();
3847N/A if (st.ttype != StreamTokenizer.TT_EOF) {
0N/A throw new FileFormatException(st.toString());
3847N/A }
0N/A }
0N/A
0N/A /** Add a vertex to this model */
0N/A int addVert(float x, float y, float z) {
0N/A int i = nvert;
3847N/A if (i >= maxvert) {
0N/A if (vert == null) {
0N/A maxvert = 100;
0N/A vert = new float[maxvert * 3];
0N/A } else {
0N/A maxvert *= 2;
0N/A float nv[] = new float[maxvert * 3];
0N/A System.arraycopy(vert, 0, nv, 0, vert.length);
0N/A vert = nv;
0N/A }
3847N/A }
0N/A i *= 3;
0N/A vert[i] = x;
0N/A vert[i + 1] = y;
0N/A vert[i + 2] = z;
0N/A return nvert++;
0N/A }
3847N/A
0N/A /** Add a line from vertex p1 to vertex p2 */
0N/A void add(int p1, int p2) {
0N/A int i = ncon;
3847N/A if (p1 >= nvert || p2 >= nvert) {
0N/A return;
3847N/A }
3847N/A if (i >= maxcon) {
0N/A if (con == null) {
0N/A maxcon = 100;
0N/A con = new int[maxcon];
0N/A } else {
0N/A maxcon *= 2;
0N/A int nv[] = new int[maxcon];
0N/A System.arraycopy(con, 0, nv, 0, con.length);
0N/A con = nv;
0N/A }
3847N/A }
0N/A if (p1 > p2) {
0N/A int t = p1;
0N/A p1 = p2;
0N/A p2 = t;
0N/A }
0N/A con[i] = (p1 << 16) | p2;
0N/A ncon = i + 1;
0N/A }
3847N/A
0N/A /** Transform all the points in this model */
0N/A void transform() {
3847N/A if (transformed || nvert <= 0) {
0N/A return;
3847N/A }
3847N/A if (tvert == null || tvert.length < nvert * 3) {
3847N/A tvert = new int[nvert * 3];
3847N/A }
0N/A mat.transform(vert, tvert, nvert);
0N/A transformed = true;
0N/A }
0N/A
3847N/A /* Quick Sort implementation
3847N/A */
3847N/A private void quickSort(int a[], int left, int right) {
3847N/A int leftIndex = left;
3847N/A int rightIndex = right;
3847N/A int partionElement;
3847N/A if (right > left) {
0N/A
3847N/A /* Arbitrarily establishing partition element as the midpoint of
3847N/A * the array.
3847N/A */
3847N/A partionElement = a[(left + right) / 2];
0N/A
3847N/A // loop through the array until indices cross
3847N/A while (leftIndex <= rightIndex) {
3847N/A /* find the first element that is greater than or equal to
3847N/A * the partionElement starting from the leftIndex.
3847N/A */
3847N/A while ((leftIndex < right) && (a[leftIndex] < partionElement)) {
3847N/A ++leftIndex;
3847N/A }
0N/A
3847N/A /* find an element that is smaller than or equal to
3847N/A * the partionElement starting from the rightIndex.
3847N/A */
3847N/A while ((rightIndex > left) && (a[rightIndex] > partionElement)) {
3847N/A --rightIndex;
3847N/A }
0N/A
3847N/A // if the indexes have not crossed, swap
3847N/A if (leftIndex <= rightIndex) {
3847N/A swap(a, leftIndex, rightIndex);
3847N/A ++leftIndex;
3847N/A --rightIndex;
3847N/A }
0N/A }
0N/A
3847N/A /* If the right index has not reached the left side of array
3847N/A * must now sort the left partition.
3847N/A */
3847N/A if (left < rightIndex) {
3847N/A quickSort(a, left, rightIndex);
3847N/A }
0N/A
3847N/A /* If the left index has not reached the right side of array
3847N/A * must now sort the right partition.
3847N/A */
3847N/A if (leftIndex < right) {
3847N/A quickSort(a, leftIndex, right);
3847N/A }
0N/A
3847N/A }
3847N/A }
0N/A
3847N/A private void swap(int a[], int i, int j) {
3847N/A int T;
3847N/A T = a[i];
3847N/A a[i] = a[j];
3847N/A a[j] = T;
3847N/A }
0N/A
0N/A /** eliminate duplicate lines */
0N/A void compress() {
0N/A int limit = ncon;
0N/A int c[] = con;
0N/A quickSort(con, 0, ncon - 1);
0N/A int d = 0;
0N/A int pp1 = -1;
0N/A for (int i = 0; i < limit; i++) {
0N/A int p1 = c[i];
0N/A if (pp1 != p1) {
0N/A c[d] = p1;
0N/A d++;
0N/A }
0N/A pp1 = p1;
0N/A }
0N/A ncon = d;
0N/A }
0N/A static Color gr[];
0N/A
0N/A /** Paint this model to a graphics context. It uses the matrix associated
3847N/A with this model to map from model space to screen space.
3847N/A The next version of the browser should have double buffering,
3847N/A which will make this *much* nicer */
0N/A void paint(Graphics g) {
3847N/A if (vert == null || nvert <= 0) {
0N/A return;
3847N/A }
0N/A transform();
0N/A if (gr == null) {
0N/A gr = new Color[16];
0N/A for (int i = 0; i < 16; i++) {
3847N/A int grey = (int) (170 * (1 - Math.pow(i / 15.0, 2.3)));
0N/A gr[i] = new Color(grey, grey, grey);
0N/A }
0N/A }
0N/A int lg = 0;
0N/A int lim = ncon;
0N/A int c[] = con;
0N/A int v[] = tvert;
3847N/A if (lim <= 0 || nvert <= 0) {
0N/A return;
3847N/A }
0N/A for (int i = 0; i < lim; i++) {
0N/A int T = c[i];
0N/A int p1 = ((T >> 16) & 0xFFFF) * 3;
0N/A int p2 = (T & 0xFFFF) * 3;
0N/A int grey = v[p1 + 2] + v[p2 + 2];
3847N/A if (grey < 0) {
0N/A grey = 0;
3847N/A }
3847N/A if (grey > 15) {
0N/A grey = 15;
3847N/A }
0N/A if (grey != lg) {
0N/A lg = grey;
0N/A g.setColor(gr[grey]);
0N/A }
0N/A g.drawLine(v[p1], v[p1 + 1],
3847N/A v[p2], v[p2 + 1]);
0N/A }
0N/A }
0N/A
0N/A /** Find the bounding box of this model */
0N/A void findBB() {
3847N/A if (nvert <= 0) {
0N/A return;
3847N/A }
0N/A float v[] = vert;
3847N/A float _xmin = v[0], _xmax = _xmin;
3847N/A float _ymin = v[1], _ymax = _ymin;
3847N/A float _zmin = v[2], _zmax = _zmin;
0N/A for (int i = nvert * 3; (i -= 3) > 0;) {
0N/A float x = v[i];
3847N/A if (x < _xmin) {
3847N/A _xmin = x;
3847N/A }
3847N/A if (x > _xmax) {
3847N/A _xmax = x;
3847N/A }
0N/A float y = v[i + 1];
3847N/A if (y < _ymin) {
3847N/A _ymin = y;
3847N/A }
3847N/A if (y > _ymax) {
3847N/A _ymax = y;
3847N/A }
0N/A float z = v[i + 2];
3847N/A if (z < _zmin) {
3847N/A _zmin = z;
3847N/A }
3847N/A if (z > _zmax) {
3847N/A _zmax = z;
3847N/A }
0N/A }
3847N/A this.xmax = _xmax;
3847N/A this.xmin = _xmin;
3847N/A this.ymax = _ymax;
3847N/A this.ymin = _ymin;
3847N/A this.zmax = _zmax;
3847N/A this.zmin = _zmin;
0N/A }
0N/A}
0N/A
3847N/A
0N/A/** An applet to put a 3D model into a page */
3847N/A@SuppressWarnings("serial")
0N/Apublic class ThreeD extends Applet
3847N/A implements Runnable, MouseListener, MouseMotionListener {
3847N/A
0N/A Model3D md;
0N/A boolean painted = true;
0N/A float xfac;
0N/A int prevx, prevy;
0N/A float scalefudge = 1;
0N/A Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
0N/A String mdname = null;
0N/A String message = null;
0N/A
3847N/A @Override
0N/A public void init() {
0N/A mdname = getParameter("model");
0N/A try {
0N/A scalefudge = Float.valueOf(getParameter("scale")).floatValue();
3847N/A } catch (Exception ignored) {
3847N/A // fall back to default scalefudge = 1
3847N/A }
0N/A amat.yrot(20);
0N/A amat.xrot(20);
3847N/A if (mdname == null) {
0N/A mdname = "model.obj";
3847N/A }
0N/A resize(getSize().width <= 20 ? 400 : getSize().width,
3847N/A getSize().height <= 20 ? 400 : getSize().height);
0N/A addMouseListener(this);
0N/A addMouseMotionListener(this);
0N/A }
0N/A
3847N/A @Override
0N/A public void destroy() {
0N/A removeMouseListener(this);
0N/A removeMouseMotionListener(this);
0N/A }
0N/A
3847N/A @Override
0N/A public void run() {
0N/A InputStream is = null;
0N/A try {
0N/A Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
0N/A is = new URL(getDocumentBase(), mdname).openStream();
3847N/A Model3D m = new Model3D(is);
0N/A md = m;
0N/A m.findBB();
0N/A m.compress();
0N/A float xw = m.xmax - m.xmin;
0N/A float yw = m.ymax - m.ymin;
0N/A float zw = m.zmax - m.zmin;
3847N/A if (yw > xw) {
0N/A xw = yw;
3847N/A }
3847N/A if (zw > xw) {
0N/A xw = zw;
3847N/A }
0N/A float f1 = getSize().width / xw;
0N/A float f2 = getSize().height / xw;
0N/A xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
3847N/A } catch (Exception e) {
0N/A md = null;
0N/A message = e.toString();
0N/A }
0N/A try {
3847N/A if (is != null) {
0N/A is.close();
3847N/A }
3847N/A } catch (Exception e) {
0N/A }
0N/A repaint();
0N/A }
0N/A
3847N/A @Override
0N/A public void start() {
3847N/A if (md == null && message == null) {
0N/A new Thread(this).start();
3847N/A }
0N/A }
0N/A
3847N/A @Override
0N/A public void stop() {
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseClicked(MouseEvent e) {
0N/A }
0N/A
3847N/A @Override
3847N/A public void mousePressed(MouseEvent e) {
0N/A prevx = e.getX();
0N/A prevy = e.getY();
0N/A e.consume();
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseReleased(MouseEvent e) {
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseEntered(MouseEvent e) {
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseExited(MouseEvent e) {
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseDragged(MouseEvent e) {
0N/A int x = e.getX();
0N/A int y = e.getY();
0N/A
0N/A tmat.unit();
0N/A float xtheta = (prevy - y) * 360.0f / getSize().width;
0N/A float ytheta = (x - prevx) * 360.0f / getSize().height;
0N/A tmat.xrot(xtheta);
0N/A tmat.yrot(ytheta);
0N/A amat.mult(tmat);
0N/A if (painted) {
0N/A painted = false;
0N/A repaint();
0N/A }
0N/A prevx = x;
0N/A prevy = y;
0N/A e.consume();
0N/A }
0N/A
3847N/A @Override
3847N/A public void mouseMoved(MouseEvent e) {
0N/A }
0N/A
3847N/A @Override
0N/A public void paint(Graphics g) {
0N/A if (md != null) {
0N/A md.mat.unit();
0N/A md.mat.translate(-(md.xmin + md.xmax) / 2,
3847N/A -(md.ymin + md.ymax) / 2,
3847N/A -(md.zmin + md.zmax) / 2);
0N/A md.mat.mult(amat);
0N/A md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
0N/A md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
0N/A md.transformed = false;
0N/A md.paint(g);
0N/A setPainted();
0N/A } else if (message != null) {
0N/A g.drawString("Error in model:", 3, 20);
0N/A g.drawString(message, 10, 40);
0N/A }
0N/A }
0N/A
0N/A private synchronized void setPainted() {
0N/A painted = true;
0N/A notifyAll();
0N/A }
0N/A
3847N/A @Override
0N/A public String getAppletInfo() {
3847N/A return "Title: ThreeD \nAuthor: James Gosling? \n"
3847N/A + "An applet to put a 3D model into a page.";
0N/A }
0N/A
3847N/A @Override
0N/A public String[][] getParameterInfo() {
0N/A String[][] info = {
3847N/A { "model", "path string", "The path to the model to be displayed." },
3847N/A { "scale", "float", "The scale of the model. Default is 1." }
0N/A };
0N/A return info;
0N/A }
0N/A}