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 
42 import java.applet.Applet;
43 import java.awt.Graphics;
44 import java.awt.Color;
45 import java.awt.event.*;
46 import java.io.*;
47 import java.net.URL;
48 
49 
50 /* A set of classes to parse, represent and display 3D wireframe models
51 represented in Wavefront .obj format. */
52 @SuppressWarnings("serial")
53 class FileFormatException extends Exception {
54 
FileFormatException(String s)55     public FileFormatException(String s) {
56         super(s);
57     }
58 }
59 
60 
61 /** The representation of a 3D model */
62 final 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 
Model3D()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 */
Model3D(InputStream is)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 */
addVert(float x, float y, float z)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 */
add(int p1, int 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 */
transform()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      */
quickSort(int a[], int left, int right)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 
swap(int a[], int i, int j)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 */
compress()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 */
paint(Graphics g)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 */
findBB()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")
377 public 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
init()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
destroy()409     public void destroy() {
410         removeMouseListener(this);
411         removeMouseMotionListener(this);
412     }
413 
414     @Override
run()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
start()450     public void start() {
451         if (md == null && message == null) {
452             new Thread(this).start();
453         }
454     }
455 
456     @Override
stop()457     public void stop() {
458     }
459 
460     @Override
mouseClicked(MouseEvent e)461     public void mouseClicked(MouseEvent e) {
462     }
463 
464     @Override
mousePressed(MouseEvent e)465     public void mousePressed(MouseEvent e) {
466         prevx = e.getX();
467         prevy = e.getY();
468         e.consume();
469     }
470 
471     @Override
mouseReleased(MouseEvent e)472     public void mouseReleased(MouseEvent e) {
473     }
474 
475     @Override
mouseEntered(MouseEvent e)476     public void mouseEntered(MouseEvent e) {
477     }
478 
479     @Override
mouseExited(MouseEvent e)480     public void mouseExited(MouseEvent e) {
481     }
482 
483     @Override
mouseDragged(MouseEvent e)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
mouseMoved(MouseEvent e)504     public void mouseMoved(MouseEvent e) {
505     }
506 
507     @Override
paint(Graphics g)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 
setPainted()526     private synchronized void setPainted() {
527         painted = true;
528         notifyAll();
529     }
530 
531     @Override
getAppletInfo()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
getParameterInfo()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