blob: b3fcd28e64a28b6a443bdddea4dc702698041344 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1995-2006 Sun Microsystems, Inc. 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 Sun Microsystems 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 */
34
35/* A set of classes to parse, represent and display 3D wireframe models
36 represented in Wavefront .obj format. */
37
38import java.applet.Applet;
39import java.awt.Graphics;
40import java.awt.Color;
41import java.awt.Event;
42import java.awt.event.*;
43import java.io.*;
44import java.net.URL;
45
46class FileFormatException extends Exception {
47 public FileFormatException(String s) {
48 super(s);
49 }
50}
51
52/** The representation of a 3D model */
53class Model3D {
54 float vert[];
55 int tvert[];
56 int nvert, maxvert;
57 int con[];
58 int ncon, maxcon;
59 boolean transformed;
60 Matrix3D mat;
61
62 float xmin, xmax, ymin, ymax, zmin, zmax;
63
64 Model3D () {
65 mat = new Matrix3D ();
66 mat.xrot(20);
67 mat.yrot(30);
68 }
69 /** Create a 3D model by parsing an input stream */
70 Model3D (InputStream is) throws IOException, FileFormatException {
71 this();
72 StreamTokenizer st = new StreamTokenizer(
73 new BufferedReader(new InputStreamReader(is, "UTF-8")));
74 st.eolIsSignificant(true);
75 st.commentChar('#');
76 scan:
77 while (true) {
78 switch (st.nextToken()) {
79 default:
80 break scan;
81 case StreamTokenizer.TT_EOL:
82 break;
83 case StreamTokenizer.TT_WORD:
84 if ("v".equals(st.sval)) {
85 double x = 0, y = 0, z = 0;
86 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
87 x = st.nval;
88 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
89 y = st.nval;
90 if (st.nextToken() == StreamTokenizer.TT_NUMBER)
91 z = st.nval;
92 }
93 }
94 addVert((float) x, (float) y, (float) z);
95 while (st.ttype != StreamTokenizer.TT_EOL &&
96 st.ttype != StreamTokenizer.TT_EOF)
97 st.nextToken();
98 } else if ("f".equals(st.sval) || "fo".equals(st.sval) || "l".equals(st.sval)) {
99 int start = -1;
100 int prev = -1;
101 int n = -1;
102 while (true)
103 if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
104 n = (int) st.nval;
105 if (prev >= 0)
106 add(prev - 1, n - 1);
107 if (start < 0)
108 start = n;
109 prev = n;
110 } else if (st.ttype == '/')
111 st.nextToken();
112 else
113 break;
114 if (start >= 0)
115 add(start - 1, prev - 1);
116 if (st.ttype != StreamTokenizer.TT_EOL)
117 break scan;
118 } else {
119 while (st.nextToken() != StreamTokenizer.TT_EOL
120 && st.ttype != StreamTokenizer.TT_EOF);
121 }
122 }
123 }
124 is.close();
125 if (st.ttype != StreamTokenizer.TT_EOF)
126 throw new FileFormatException(st.toString());
127 }
128
129 /** Add a vertex to this model */
130 int addVert(float x, float y, float z) {
131 int i = nvert;
132 if (i >= maxvert)
133 if (vert == null) {
134 maxvert = 100;
135 vert = new float[maxvert * 3];
136 } else {
137 maxvert *= 2;
138 float nv[] = new float[maxvert * 3];
139 System.arraycopy(vert, 0, nv, 0, vert.length);
140 vert = nv;
141 }
142 i *= 3;
143 vert[i] = x;
144 vert[i + 1] = y;
145 vert[i + 2] = z;
146 return nvert++;
147 }
148 /** Add a line from vertex p1 to vertex p2 */
149 void add(int p1, int p2) {
150 int i = ncon;
151 if (p1 >= nvert || p2 >= nvert)
152 return;
153 if (i >= maxcon)
154 if (con == null) {
155 maxcon = 100;
156 con = new int[maxcon];
157 } else {
158 maxcon *= 2;
159 int nv[] = new int[maxcon];
160 System.arraycopy(con, 0, nv, 0, con.length);
161 con = nv;
162 }
163 if (p1 > p2) {
164 int t = p1;
165 p1 = p2;
166 p2 = t;
167 }
168 con[i] = (p1 << 16) | p2;
169 ncon = i + 1;
170 }
171 /** Transform all the points in this model */
172 void transform() {
173 if (transformed || nvert <= 0)
174 return;
175 if (tvert == null || tvert.length < nvert * 3)
176 tvert = new int[nvert*3];
177 mat.transform(vert, tvert, nvert);
178 transformed = true;
179 }
180
181 /* Quick Sort implementation
182 */
183 private void quickSort(int a[], int left, int right)
184 {
185 int leftIndex = left;
186 int rightIndex = right;
187 int partionElement;
188 if ( right > left)
189 {
190
191 /* Arbitrarily establishing partition element as the midpoint of
192 * the array.
193 */
194 partionElement = a[ ( left + right ) / 2 ];
195
196 // loop through the array until indices cross
197 while( leftIndex <= rightIndex )
198 {
199 /* find the first element that is greater than or equal to
200 * the partionElement starting from the leftIndex.
201 */
202 while( ( leftIndex < right ) && ( a[leftIndex] < partionElement ) )
203 ++leftIndex;
204
205 /* find an element that is smaller than or equal to
206 * the partionElement starting from the rightIndex.
207 */
208 while( ( rightIndex > left ) &&
209 ( a[rightIndex] > partionElement ) )
210 --rightIndex;
211
212 // if the indexes have not crossed, swap
213 if( leftIndex <= rightIndex )
214 {
215 swap(a, leftIndex, rightIndex);
216 ++leftIndex;
217 --rightIndex;
218 }
219 }
220
221 /* If the right index has not reached the left side of array
222 * must now sort the left partition.
223 */
224 if( left < rightIndex )
225 quickSort( a, left, rightIndex );
226
227 /* If the left index has not reached the right side of array
228 * must now sort the right partition.
229 */
230 if( leftIndex < right )
231 quickSort( a, leftIndex, right );
232
233 }
234 }
235
236 private void swap(int a[], int i, int j)
237 {
238 int T;
239 T = a[i];
240 a[i] = a[j];
241 a[j] = T;
242 }
243
244
245 /** eliminate duplicate lines */
246 void compress() {
247 int limit = ncon;
248 int c[] = con;
249 quickSort(con, 0, ncon - 1);
250 int d = 0;
251 int pp1 = -1;
252 for (int i = 0; i < limit; i++) {
253 int p1 = c[i];
254 if (pp1 != p1) {
255 c[d] = p1;
256 d++;
257 }
258 pp1 = p1;
259 }
260 ncon = d;
261 }
262
263 static Color gr[];
264
265 /** Paint this model to a graphics context. It uses the matrix associated
266 with this model to map from model space to screen space.
267 The next version of the browser should have double buffering,
268 which will make this *much* nicer */
269 void paint(Graphics g) {
270 if (vert == null || nvert <= 0)
271 return;
272 transform();
273 if (gr == null) {
274 gr = new Color[16];
275 for (int i = 0; i < 16; i++) {
276 int grey = (int) (170*(1-Math.pow(i/15.0, 2.3)));
277 gr[i] = new Color(grey, grey, grey);
278 }
279 }
280 int lg = 0;
281 int lim = ncon;
282 int c[] = con;
283 int v[] = tvert;
284 if (lim <= 0 || nvert <= 0)
285 return;
286 for (int i = 0; i < lim; i++) {
287 int T = c[i];
288 int p1 = ((T >> 16) & 0xFFFF) * 3;
289 int p2 = (T & 0xFFFF) * 3;
290 int grey = v[p1 + 2] + v[p2 + 2];
291 if (grey < 0)
292 grey = 0;
293 if (grey > 15)
294 grey = 15;
295 if (grey != lg) {
296 lg = grey;
297 g.setColor(gr[grey]);
298 }
299 g.drawLine(v[p1], v[p1 + 1],
300 v[p2], v[p2 + 1]);
301 }
302 }
303
304 /** Find the bounding box of this model */
305 void findBB() {
306 if (nvert <= 0)
307 return;
308 float v[] = vert;
309 float xmin = v[0], xmax = xmin;
310 float ymin = v[1], ymax = ymin;
311 float zmin = v[2], zmax = zmin;
312 for (int i = nvert * 3; (i -= 3) > 0;) {
313 float x = v[i];
314 if (x < xmin)
315 xmin = x;
316 if (x > xmax)
317 xmax = x;
318 float y = v[i + 1];
319 if (y < ymin)
320 ymin = y;
321 if (y > ymax)
322 ymax = y;
323 float z = v[i + 2];
324 if (z < zmin)
325 zmin = z;
326 if (z > zmax)
327 zmax = z;
328 }
329 this.xmax = xmax;
330 this.xmin = xmin;
331 this.ymax = ymax;
332 this.ymin = ymin;
333 this.zmax = zmax;
334 this.zmin = zmin;
335 }
336}
337
338/** An applet to put a 3D model into a page */
339public class ThreeD extends Applet
340 implements Runnable, MouseListener, MouseMotionListener {
341 Model3D md;
342 boolean painted = true;
343 float xfac;
344 int prevx, prevy;
345 float xtheta, ytheta;
346 float scalefudge = 1;
347 Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
348 String mdname = null;
349 String message = null;
350
351 public void init() {
352 mdname = getParameter("model");
353 try {
354 scalefudge = Float.valueOf(getParameter("scale")).floatValue();
355 }catch(Exception e){};
356 amat.yrot(20);
357 amat.xrot(20);
358 if (mdname == null)
359 mdname = "model.obj";
360 resize(getSize().width <= 20 ? 400 : getSize().width,
361 getSize().height <= 20 ? 400 : getSize().height);
362 addMouseListener(this);
363 addMouseMotionListener(this);
364 }
365
366 public void destroy() {
367 removeMouseListener(this);
368 removeMouseMotionListener(this);
369 }
370
371 public void run() {
372 InputStream is = null;
373 try {
374 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
375 is = new URL(getDocumentBase(), mdname).openStream();
376 Model3D m = new Model3D (is);
377 md = m;
378 m.findBB();
379 m.compress();
380 float xw = m.xmax - m.xmin;
381 float yw = m.ymax - m.ymin;
382 float zw = m.zmax - m.zmin;
383 if (yw > xw)
384 xw = yw;
385 if (zw > xw)
386 xw = zw;
387 float f1 = getSize().width / xw;
388 float f2 = getSize().height / xw;
389 xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
390 } catch(Exception e) {
391 md = null;
392 message = e.toString();
393 }
394 try {
395 if (is != null)
396 is.close();
397 } catch(Exception e) {
398 }
399 repaint();
400 }
401
402 public void start() {
403 if (md == null && message == null)
404 new Thread(this).start();
405 }
406
407 public void stop() {
408 }
409
410 public void mouseClicked(MouseEvent e) {
411 }
412
413 public void mousePressed(MouseEvent e) {
414 prevx = e.getX();
415 prevy = e.getY();
416 e.consume();
417 }
418
419 public void mouseReleased(MouseEvent e) {
420 }
421
422 public void mouseEntered(MouseEvent e) {
423 }
424
425 public void mouseExited(MouseEvent e) {
426 }
427
428 public void mouseDragged(MouseEvent e) {
429 int x = e.getX();
430 int y = e.getY();
431
432 tmat.unit();
433 float xtheta = (prevy - y) * 360.0f / getSize().width;
434 float ytheta = (x - prevx) * 360.0f / getSize().height;
435 tmat.xrot(xtheta);
436 tmat.yrot(ytheta);
437 amat.mult(tmat);
438 if (painted) {
439 painted = false;
440 repaint();
441 }
442 prevx = x;
443 prevy = y;
444 e.consume();
445 }
446
447 public void mouseMoved(MouseEvent e) {
448 }
449
450 public void paint(Graphics g) {
451 if (md != null) {
452 md.mat.unit();
453 md.mat.translate(-(md.xmin + md.xmax) / 2,
454 -(md.ymin + md.ymax) / 2,
455 -(md.zmin + md.zmax) / 2);
456 md.mat.mult(amat);
457 md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
458 md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
459 md.transformed = false;
460 md.paint(g);
461 setPainted();
462 } else if (message != null) {
463 g.drawString("Error in model:", 3, 20);
464 g.drawString(message, 10, 40);
465 }
466 }
467
468 private synchronized void setPainted() {
469 painted = true;
470 notifyAll();
471 }
472// private synchronized void waitPainted() {
473// while (!painted)
474// wait();
475// painted = false;
476// }
477
478 public String getAppletInfo() {
479 return "Title: ThreeD \nAuthor: James Gosling? \nAn applet to put a 3D model into a page.";
480 }
481
482 public String[][] getParameterInfo() {
483 String[][] info = {
484 {"model", "path string", "The path to the model to be displayed."},
485 {"scale", "float", "The scale of the model. Default is 1."}
486 };
487 return info;
488 }
489}