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