1/*
2 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 *   - Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 *
11 *   - Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 *   - Neither the name of Oracle nor the names of its
16 *     contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * This source code is provided to illustrate the usage of a given feature
34 * or technique and has been deliberately simplified. Additional steps
35 * required for a production-quality application, such as security checks,
36 * input validation and proper error handling, might not be present in
37 * this sample code.
38 */
39
40
41
42import java.applet.Applet;
43import java.awt.Graphics;
44import java.awt.Color;
45import java.awt.event.*;
46import java.io.*;
47import java.net.URL;
48
49
50/* A set of classes to parse, represent and display 3D wireframe models
51represented in Wavefront .obj format. */
52@SuppressWarnings("serial")
53class FileFormatException extends Exception {
54
55    public FileFormatException(String s) {
56        super(s);
57    }
58}
59
60
61/** The representation of a 3D model */
62final class Model3D {
63
64    float vert[];
65    int tvert[];
66    int nvert, maxvert;
67    int con[];
68    int ncon, maxcon;
69    boolean transformed;
70    Matrix3D mat;
71    float xmin, xmax, ymin, ymax, zmin, zmax;
72
73    Model3D() {
74        mat = new Matrix3D();
75        mat.xrot(20);
76        mat.yrot(30);
77    }
78
79    /** Create a 3D model by parsing an input stream */
80    Model3D(InputStream is) throws IOException, FileFormatException {
81        this();
82        StreamTokenizer st = new StreamTokenizer(
83                new BufferedReader(new InputStreamReader(is, "UTF-8")));
84        st.eolIsSignificant(true);
85        st.commentChar('#');
86        scan:
87        while (true) {
88            switch (st.nextToken()) {
89                default:
90                    break scan;
91                case StreamTokenizer.TT_EOL:
92                    break;
93                case StreamTokenizer.TT_WORD:
94                    if ("v".equals(st.sval)) {
95                        double x = 0, y = 0, z = 0;
96                        if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
97                            x = st.nval;
98                            if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
99                                y = st.nval;
100                                if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
101                                    z = st.nval;
102                                }
103                            }
104                        }
105                        addVert((float) x, (float) y, (float) z);
106                        while (st.ttype != StreamTokenizer.TT_EOL && st.ttype
107                                != StreamTokenizer.TT_EOF) {
108                            st.nextToken();
109                        }
110                    } else if ("f".equals(st.sval) || "fo".equals(st.sval) || "l".
111                            equals(st.sval)) {
112                        int start = -1;
113                        int prev = -1;
114                        int n = -1;
115                        while (true) {
116                            if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
117                                n = (int) st.nval;
118                                if (prev >= 0) {
119                                    add(prev - 1, n - 1);
120                                }
121                                if (start < 0) {
122                                    start = n;
123                                }
124                                prev = n;
125                            } else if (st.ttype == '/') {
126                                st.nextToken();
127                            } else {
128                                break;
129                            }
130                        }
131                        if (start >= 0) {
132                            add(start - 1, prev - 1);
133                        }
134                        if (st.ttype != StreamTokenizer.TT_EOL) {
135                            break scan;
136                        }
137                    } else {
138                        while (st.nextToken() != StreamTokenizer.TT_EOL
139                                && st.ttype != StreamTokenizer.TT_EOF) {
140                            // no-op
141                        }
142                    }
143            }
144        }
145        is.close();
146        if (st.ttype != StreamTokenizer.TT_EOF) {
147            throw new FileFormatException(st.toString());
148        }
149    }
150
151    /** Add a vertex to this model */
152    int addVert(float x, float y, float z) {
153        int i = nvert;
154        if (i >= maxvert) {
155            if (vert == null) {
156                maxvert = 100;
157                vert = new float[maxvert * 3];
158            } else {
159                maxvert *= 2;
160                float nv[] = new float[maxvert * 3];
161                System.arraycopy(vert, 0, nv, 0, vert.length);
162                vert = nv;
163            }
164        }
165        i *= 3;
166        vert[i] = x;
167        vert[i + 1] = y;
168        vert[i + 2] = z;
169        return nvert++;
170    }
171
172    /** Add a line from vertex p1 to vertex p2 */
173    void add(int p1, int p2) {
174        int i = ncon;
175        if (p1 >= nvert || p2 >= nvert) {
176            return;
177        }
178        if (i >= maxcon) {
179            if (con == null) {
180                maxcon = 100;
181                con = new int[maxcon];
182            } else {
183                maxcon *= 2;
184                int nv[] = new int[maxcon];
185                System.arraycopy(con, 0, nv, 0, con.length);
186                con = nv;
187            }
188        }
189        if (p1 > p2) {
190            int t = p1;
191            p1 = p2;
192            p2 = t;
193        }
194        con[i] = (p1 << 16) | p2;
195        ncon = i + 1;
196    }
197
198    /** Transform all the points in this model */
199    void transform() {
200        if (transformed || nvert <= 0) {
201            return;
202        }
203        if (tvert == null || tvert.length < nvert * 3) {
204            tvert = new int[nvert * 3];
205        }
206        mat.transform(vert, tvert, nvert);
207        transformed = true;
208    }
209
210    /* Quick Sort implementation
211     */
212    private void quickSort(int a[], int left, int right) {
213        int leftIndex = left;
214        int rightIndex = right;
215        int partionElement;
216        if (right > left) {
217
218            /* Arbitrarily establishing partition element as the midpoint of
219             * the array.
220             */
221            partionElement = a[(left + right) / 2];
222
223            // loop through the array until indices cross
224            while (leftIndex <= rightIndex) {
225                /* find the first element that is greater than or equal to
226                 * the partionElement starting from the leftIndex.
227                 */
228                while ((leftIndex < right) && (a[leftIndex] < partionElement)) {
229                    ++leftIndex;
230                }
231
232                /* find an element that is smaller than or equal to
233                 * the partionElement starting from the rightIndex.
234                 */
235                while ((rightIndex > left) && (a[rightIndex] > partionElement)) {
236                    --rightIndex;
237                }
238
239                // if the indexes have not crossed, swap
240                if (leftIndex <= rightIndex) {
241                    swap(a, leftIndex, rightIndex);
242                    ++leftIndex;
243                    --rightIndex;
244                }
245            }
246
247            /* If the right index has not reached the left side of array
248             * must now sort the left partition.
249             */
250            if (left < rightIndex) {
251                quickSort(a, left, rightIndex);
252            }
253
254            /* If the left index has not reached the right side of array
255             * must now sort the right partition.
256             */
257            if (leftIndex < right) {
258                quickSort(a, leftIndex, right);
259            }
260
261        }
262    }
263
264    private void swap(int a[], int i, int j) {
265        int T;
266        T = a[i];
267        a[i] = a[j];
268        a[j] = T;
269    }
270
271    /** eliminate duplicate lines */
272    void compress() {
273        int limit = ncon;
274        int c[] = con;
275        quickSort(con, 0, ncon - 1);
276        int d = 0;
277        int pp1 = -1;
278        for (int i = 0; i < limit; i++) {
279            int p1 = c[i];
280            if (pp1 != p1) {
281                c[d] = p1;
282                d++;
283            }
284            pp1 = p1;
285        }
286        ncon = d;
287    }
288    static Color gr[];
289
290    /** Paint this model to a graphics context.  It uses the matrix associated
291    with this model to map from model space to screen space.
292    The next version of the browser should have double buffering,
293    which will make this *much* nicer */
294    void paint(Graphics g) {
295        if (vert == null || nvert <= 0) {
296            return;
297        }
298        transform();
299        if (gr == null) {
300            gr = new Color[16];
301            for (int i = 0; i < 16; i++) {
302                int grey = (int) (170 * (1 - Math.pow(i / 15.0, 2.3)));
303                gr[i] = new Color(grey, grey, grey);
304            }
305        }
306        int lg = 0;
307        int lim = ncon;
308        int c[] = con;
309        int v[] = tvert;
310        if (lim <= 0 || nvert <= 0) {
311            return;
312        }
313        for (int i = 0; i < lim; i++) {
314            int T = c[i];
315            int p1 = ((T >> 16) & 0xFFFF) * 3;
316            int p2 = (T & 0xFFFF) * 3;
317            int grey = v[p1 + 2] + v[p2 + 2];
318            if (grey < 0) {
319                grey = 0;
320            }
321            if (grey > 15) {
322                grey = 15;
323            }
324            if (grey != lg) {
325                lg = grey;
326                g.setColor(gr[grey]);
327            }
328            g.drawLine(v[p1], v[p1 + 1],
329                    v[p2], v[p2 + 1]);
330        }
331    }
332
333    /** Find the bounding box of this model */
334    void findBB() {
335        if (nvert <= 0) {
336            return;
337        }
338        float v[] = vert;
339        float _xmin = v[0], _xmax = _xmin;
340        float _ymin = v[1], _ymax = _ymin;
341        float _zmin = v[2], _zmax = _zmin;
342        for (int i = nvert * 3; (i -= 3) > 0;) {
343            float x = v[i];
344            if (x < _xmin) {
345                _xmin = x;
346            }
347            if (x > _xmax) {
348                _xmax = x;
349            }
350            float y = v[i + 1];
351            if (y < _ymin) {
352                _ymin = y;
353            }
354            if (y > _ymax) {
355                _ymax = y;
356            }
357            float z = v[i + 2];
358            if (z < _zmin) {
359                _zmin = z;
360            }
361            if (z > _zmax) {
362                _zmax = z;
363            }
364        }
365        this.xmax = _xmax;
366        this.xmin = _xmin;
367        this.ymax = _ymax;
368        this.ymin = _ymin;
369        this.zmax = _zmax;
370        this.zmin = _zmin;
371    }
372}
373
374
375/** An applet to put a 3D model into a page */
376@SuppressWarnings("serial")
377public class ThreeD extends Applet
378        implements Runnable, MouseListener, MouseMotionListener {
379
380    Model3D md;
381    boolean painted = true;
382    float xfac;
383    int prevx, prevy;
384    float scalefudge = 1;
385    Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
386    String mdname = null;
387    String message = null;
388
389    @Override
390    public void init() {
391        mdname = getParameter("model");
392        try {
393            scalefudge = Float.valueOf(getParameter("scale")).floatValue();
394        } catch (Exception ignored) {
395            // fall back to default scalefudge = 1
396        }
397        amat.yrot(20);
398        amat.xrot(20);
399        if (mdname == null) {
400            mdname = "model.obj";
401        }
402        resize(getSize().width <= 20 ? 400 : getSize().width,
403                getSize().height <= 20 ? 400 : getSize().height);
404        addMouseListener(this);
405        addMouseMotionListener(this);
406    }
407
408    @Override
409    public void destroy() {
410        removeMouseListener(this);
411        removeMouseMotionListener(this);
412    }
413
414    @Override
415    public void run() {
416        InputStream is = null;
417        try {
418            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
419            is = getClass().getResourceAsStream(mdname);
420            Model3D m = new Model3D(is);
421            md = m;
422            m.findBB();
423            m.compress();
424            float xw = m.xmax - m.xmin;
425            float yw = m.ymax - m.ymin;
426            float zw = m.zmax - m.zmin;
427            if (yw > xw) {
428                xw = yw;
429            }
430            if (zw > xw) {
431                xw = zw;
432            }
433            float f1 = getSize().width / xw;
434            float f2 = getSize().height / xw;
435            xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
436        } catch (Exception e) {
437            md = null;
438            message = e.toString();
439        }
440        try {
441            if (is != null) {
442                is.close();
443            }
444        } catch (Exception e) {
445        }
446        repaint();
447    }
448
449    @Override
450    public void start() {
451        if (md == null && message == null) {
452            new Thread(this).start();
453        }
454    }
455
456    @Override
457    public void stop() {
458    }
459
460    @Override
461    public void mouseClicked(MouseEvent e) {
462    }
463
464    @Override
465    public void mousePressed(MouseEvent e) {
466        prevx = e.getX();
467        prevy = e.getY();
468        e.consume();
469    }
470
471    @Override
472    public void mouseReleased(MouseEvent e) {
473    }
474
475    @Override
476    public void mouseEntered(MouseEvent e) {
477    }
478
479    @Override
480    public void mouseExited(MouseEvent e) {
481    }
482
483    @Override
484    public void mouseDragged(MouseEvent e) {
485        int x = e.getX();
486        int y = e.getY();
487
488        tmat.unit();
489        float xtheta = (prevy - y) * 360.0f / getSize().width;
490        float ytheta = (x - prevx) * 360.0f / getSize().height;
491        tmat.xrot(xtheta);
492        tmat.yrot(ytheta);
493        amat.mult(tmat);
494        if (painted) {
495            painted = false;
496            repaint();
497        }
498        prevx = x;
499        prevy = y;
500        e.consume();
501    }
502
503    @Override
504    public void mouseMoved(MouseEvent e) {
505    }
506
507    @Override
508    public void paint(Graphics g) {
509        if (md != null) {
510            md.mat.unit();
511            md.mat.translate(-(md.xmin + md.xmax) / 2,
512                    -(md.ymin + md.ymax) / 2,
513                    -(md.zmin + md.zmax) / 2);
514            md.mat.mult(amat);
515            md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
516            md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
517            md.transformed = false;
518            md.paint(g);
519            setPainted();
520        } else if (message != null) {
521            g.drawString("Error in model:", 3, 20);
522            g.drawString(message, 10, 40);
523        }
524    }
525
526    private synchronized void setPainted() {
527        painted = true;
528        notifyAll();
529    }
530
531    @Override
532    public String getAppletInfo() {
533        return "Title: ThreeD \nAuthor: James Gosling? \n"
534                + "An applet to put a 3D model into a page.";
535    }
536
537    @Override
538    public String[][] getParameterInfo() {
539        String[][] info = {
540            { "model", "path string", "The path to the model to be displayed." },
541            { "scale", "float", "The scale of the model.  Default is 1." }
542        };
543        return info;
544    }
545}
546