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.Image; 44import java.awt.Graphics; 45import java.awt.Dimension; 46import java.awt.event.MouseEvent; 47import java.awt.event.MouseListener; 48import java.awt.event.MouseMotionListener; 49import java.net.URL; 50import java.awt.image.IndexColorModel; 51import java.awt.image.MemoryImageSource; 52import java.io.BufferedReader; 53import java.io.IOException; 54import java.io.InputStream; 55import java.io.InputStreamReader; 56import java.io.StreamTokenizer; 57import java.util.HashMap; 58import java.util.Map; 59import java.util.logging.Level; 60import java.util.logging.Logger; 61 62 63/* 64 * A set of classes to parse, represent and display Chemical compounds in 65 * .xyz format (see http://chem.leeds.ac.uk/Project/MIME.html) 66 */ 67/** The representation of a Chemical .xyz model */ 68final class XYZChemModel { 69 70 float vert[]; 71 Atom atoms[]; 72 int tvert[]; 73 int ZsortMap[]; 74 int nvert, maxvert; 75 static final Map<String, Atom> atomTable = new HashMap<String, Atom>(); 76 static Atom defaultAtom; 77 78 static { 79 atomTable.put("c", new Atom(0, 0, 0)); 80 atomTable.put("h", new Atom(210, 210, 210)); 81 atomTable.put("n", new Atom(0, 0, 255)); 82 atomTable.put("o", new Atom(255, 0, 0)); 83 atomTable.put("p", new Atom(255, 0, 255)); 84 atomTable.put("s", new Atom(255, 255, 0)); 85 atomTable.put("hn", new Atom(150, 255, 150)); /* !!*/ 86 defaultAtom = new Atom(255, 100, 200); 87 } 88 boolean transformed; 89 Matrix3D mat; 90 float xmin, xmax, ymin, ymax, zmin, zmax; 91 92 XYZChemModel() { 93 mat = new Matrix3D(); 94 mat.xrot(20); 95 mat.yrot(30); 96 } 97 98 /** Create a Chemical model by parsing an input stream */ 99 XYZChemModel(InputStream is) throws Exception { 100 this(); 101 StreamTokenizer st = new StreamTokenizer( 102 new BufferedReader(new InputStreamReader(is, "UTF-8"))); 103 st.eolIsSignificant(true); 104 st.commentChar('#'); 105 106 try { 107 scan: 108 while (true) { 109 switch (st.nextToken()) { 110 case StreamTokenizer.TT_EOF: 111 break scan; 112 default: 113 break; 114 case StreamTokenizer.TT_WORD: 115 String name = st.sval; 116 double x = 0, 117 y = 0, 118 z = 0; 119 if (st.nextToken() == StreamTokenizer.TT_NUMBER) { 120 x = st.nval; 121 if (st.nextToken() == StreamTokenizer.TT_NUMBER) { 122 y = st.nval; 123 if (st.nextToken() == StreamTokenizer.TT_NUMBER) { 124 z = st.nval; 125 } 126 } 127 } 128 addVert(name, (float) x, (float) y, (float) z); 129 while (st.ttype != StreamTokenizer.TT_EOL 130 && st.ttype != StreamTokenizer.TT_EOF) { 131 st.nextToken(); 132 } 133 134 } // end Switch 135 136 } // end while 137 138 is.close(); 139 140 } // end Try 141 catch (IOException e) { 142 } 143 144 if (st.ttype != StreamTokenizer.TT_EOF) { 145 throw new Exception(st.toString()); 146 } 147 148 } // end XYZChemModel() 149 150 /** Add a vertex to this model */ 151 int addVert(String name, float x, float y, float z) { 152 int i = nvert; 153 if (i >= maxvert) { 154 if (vert == null) { 155 maxvert = 100; 156 vert = new float[maxvert * 3]; 157 atoms = new Atom[maxvert]; 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 Atom na[] = new Atom[maxvert]; 164 System.arraycopy(atoms, 0, na, 0, atoms.length); 165 atoms = na; 166 } 167 } 168 Atom a = atomTable.get(name.toLowerCase()); 169 if (a == null) { 170 a = defaultAtom; 171 } 172 atoms[i] = a; 173 i *= 3; 174 vert[i] = x; 175 vert[i + 1] = y; 176 vert[i + 2] = z; 177 return nvert++; 178 } 179 180 /** Transform all the points in this model */ 181 void transform() { 182 if (transformed || nvert <= 0) { 183 return; 184 } 185 if (tvert == null || tvert.length < nvert * 3) { 186 tvert = new int[nvert * 3]; 187 } 188 mat.transform(vert, tvert, nvert); 189 transformed = true; 190 } 191 192 /** Paint this model to a graphics context. It uses the matrix associated 193 with this model to map from model space to screen space. 194 The next version of the browser should have double buffering, 195 which will make this *much* nicer */ 196 void paint(Graphics g) { 197 if (vert == null || nvert <= 0) { 198 return; 199 } 200 transform(); 201 int v[] = tvert; 202 int zs[] = ZsortMap; 203 if (zs == null) { 204 ZsortMap = zs = new int[nvert]; 205 for (int i = nvert; --i >= 0;) { 206 zs[i] = i * 3; 207 } 208 } 209 210 /* 211 * I use a bubble sort since from one iteration to the next, the sort 212 * order is pretty stable, so I just use what I had last time as a 213 * "guess" of the sorted order. With luck, this reduces O(N log N) 214 * to O(N) 215 */ 216 217 for (int i = nvert - 1; --i >= 0;) { 218 boolean flipped = false; 219 for (int j = 0; j <= i; j++) { 220 int a = zs[j]; 221 int b = zs[j + 1]; 222 if (v[a + 2] > v[b + 2]) { 223 zs[j + 1] = a; 224 zs[j] = b; 225 flipped = true; 226 } 227 } 228 if (!flipped) { 229 break; 230 } 231 } 232 233 int lim = nvert; 234 if (lim <= 0 || nvert <= 0) { 235 return; 236 } 237 for (int i = 0; i < lim; i++) { 238 int j = zs[i]; 239 int grey = v[j + 2]; 240 if (grey < 0) { 241 grey = 0; 242 } 243 if (grey > 15) { 244 grey = 15; 245 } 246 // g.drawString(names[i], v[j], v[j+1]); 247 atoms[j / 3].paint(g, v[j], v[j + 1], grey); 248 // g.drawImage(iBall, v[j] - (iBall.width >> 1), v[j + 1] - 249 // (iBall.height >> 1)); 250 } 251 } 252 253 /** Find the bounding box of this model */ 254 void findBB() { 255 if (nvert <= 0) { 256 return; 257 } 258 float v[] = vert; 259 float _xmin = v[0], _xmax = _xmin; 260 float _ymin = v[1], _ymax = _ymin; 261 float _zmin = v[2], _zmax = _zmin; 262 for (int i = nvert * 3; (i -= 3) > 0;) { 263 float x = v[i]; 264 if (x < _xmin) { 265 _xmin = x; 266 } 267 if (x > _xmax) { 268 _xmax = x; 269 } 270 float y = v[i + 1]; 271 if (y < _ymin) { 272 _ymin = y; 273 } 274 if (y > _ymax) { 275 _ymax = y; 276 } 277 float z = v[i + 2]; 278 if (z < _zmin) { 279 _zmin = z; 280 } 281 if (z > _zmax) { 282 _zmax = z; 283 } 284 } 285 this.xmax = _xmax; 286 this.xmin = _xmin; 287 this.ymax = _ymax; 288 this.ymin = _ymin; 289 this.zmax = _zmax; 290 this.zmin = _zmin; 291 } 292} 293 294 295/** An applet to put a Chemical model into a page */ 296@SuppressWarnings("serial") 297public class XYZApp extends Applet implements Runnable, MouseListener, 298 MouseMotionListener { 299 300 XYZChemModel md; 301 boolean painted = true; 302 float xfac; 303 int prevx, prevy; 304 float scalefudge = 1; 305 Matrix3D amat = new Matrix3D(), tmat = new Matrix3D(); 306 String mdname = null; 307 String message = null; 308 Image backBuffer; 309 Graphics backGC; 310 Dimension backSize; 311 312 private synchronized void newBackBuffer() { 313 backBuffer = createImage(getSize().width, getSize().height); 314 if (backGC != null) { 315 backGC.dispose(); 316 } 317 backGC = backBuffer.getGraphics(); 318 backSize = getSize(); 319 } 320 321 @Override 322 public void init() { 323 mdname = getParameter("model"); 324 try { 325 scalefudge = Float.valueOf(getParameter("scale")).floatValue(); 326 } catch (Exception ignored) { 327 } 328 amat.yrot(20); 329 amat.xrot(20); 330 if (mdname == null) { 331 mdname = "model.obj"; 332 } 333 resize(getSize().width <= 20 ? 400 : getSize().width, 334 getSize().height <= 20 ? 400 : getSize().height); 335 newBackBuffer(); 336 addMouseListener(this); 337 addMouseMotionListener(this); 338 } 339 340 @Override 341 public void destroy() { 342 removeMouseListener(this); 343 removeMouseMotionListener(this); 344 } 345 346 @Override 347 public void run() { 348 InputStream is = null; 349 try { 350 Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 351 is = getClass().getResourceAsStream(mdname); 352 XYZChemModel m = new XYZChemModel(is); 353 Atom.setApplet(this); 354 md = m; 355 m.findBB(); 356 float xw = m.xmax - m.xmin; 357 float yw = m.ymax - m.ymin; 358 float zw = m.zmax - m.zmin; 359 if (yw > xw) { 360 xw = yw; 361 } 362 if (zw > xw) { 363 xw = zw; 364 } 365 float f1 = getSize().width / xw; 366 float f2 = getSize().height / xw; 367 xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge; 368 } catch (Exception e) { 369 Logger.getLogger(XYZApp.class.getName()).log(Level.SEVERE, null, e); 370 md = null; 371 message = e.toString(); 372 } 373 try { 374 if (is != null) { 375 is.close(); 376 } 377 } catch (Exception ignored) { 378 } 379 repaint(); 380 } 381 382 @Override 383 public void start() { 384 if (md == null && message == null) { 385 new Thread(this).start(); 386 } 387 } 388 389 @Override 390 public void stop() { 391 } 392 /* event handling */ 393 394 @Override 395 public void mouseClicked(MouseEvent e) { 396 } 397 398 @Override 399 public void mousePressed(MouseEvent e) { 400 prevx = e.getX(); 401 prevy = e.getY(); 402 e.consume(); 403 } 404 405 @Override 406 public void mouseReleased(MouseEvent e) { 407 } 408 409 @Override 410 public void mouseEntered(MouseEvent e) { 411 } 412 413 @Override 414 public void mouseExited(MouseEvent e) { 415 } 416 417 @Override 418 public void mouseDragged(MouseEvent e) { 419 int x = e.getX(); 420 int y = e.getY(); 421 tmat.unit(); 422 float xtheta = (prevy - y) * (360.0f / getSize().width); 423 float ytheta = (x - prevx) * (360.0f / getSize().height); 424 tmat.xrot(xtheta); 425 tmat.yrot(ytheta); 426 amat.mult(tmat); 427 if (painted) { 428 painted = false; 429 repaint(); 430 } 431 prevx = x; 432 prevy = y; 433 e.consume(); 434 } 435 436 @Override 437 public void mouseMoved(MouseEvent e) { 438 } 439 440 @Override 441 public void update(Graphics g) { 442 if (backBuffer == null) { 443 g.clearRect(0, 0, getSize().width, getSize().height); 444 } 445 paint(g); 446 } 447 448 @Override 449 public void paint(Graphics g) { 450 if (md != null) { 451 md.mat.unit(); 452 md.mat.translate(-(md.xmin + md.xmax) / 2, 453 -(md.ymin + md.ymax) / 2, 454 -(md.zmin + md.zmax) / 2); 455 md.mat.mult(amat); 456 // md.mat.scale(xfac, -xfac, 8 * xfac / getSize().width); 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 if (backBuffer != null) { 461 if (!backSize.equals(getSize())) { 462 newBackBuffer(); 463 } 464 backGC.setColor(getBackground()); 465 backGC.fillRect(0, 0, getSize().width, getSize().height); 466 md.paint(backGC); 467 g.drawImage(backBuffer, 0, 0, this); 468 } else { 469 md.paint(g); 470 } 471 setPainted(); 472 } else if (message != null) { 473 g.drawString("Error in model:", 3, 20); 474 g.drawString(message, 10, 40); 475 } 476 } 477 478 private synchronized void setPainted() { 479 painted = true; 480 notifyAll(); 481 } 482 483 @Override 484 public String getAppletInfo() { 485 return "Title: XYZApp \nAuthor: James Gosling \nAn applet to put" 486 + " a Chemical model into a page."; 487 } 488 489 @Override 490 public String[][] getParameterInfo() { 491 String[][] info = { 492 { "model", "path string", "The path to the model to be displayed" 493 + " in .xyz format " 494 + "(see http://chem.leeds.ac.uk/Project/MIME.html)." 495 + " Default is model.obj." }, 496 { "scale", "float", "Scale factor. Default is 1 (i.e. no scale)." } 497 }; 498 return info; 499 } 500} // end class XYZApp 501 502 503class Atom { 504 505 private static Applet applet; 506 private static byte[] data; 507 private static final int R = 40; 508 private static final int hx = 15; 509 private static final int hy = 15; 510 private static final int bgGrey = 192; 511 private static final int nBalls = 16; 512 private static int maxr; 513 private int Rl; 514 private int Gl; 515 private int Bl; 516 private Image balls[]; 517 518 static { 519 data = new byte[R * 2 * R * 2]; 520 int mr = 0; 521 for (int Y = 2 * R; --Y >= 0;) { 522 int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5); 523 int p = Y * (R * 2) + R - x0; 524 for (int X = -x0; X < x0; X++) { 525 int x = X + hx; 526 int y = Y - R + hy; 527 int r = (int) (Math.sqrt(x * x + y * y) + 0.5); 528 if (r > mr) { 529 mr = r; 530 } 531 data[p++] = r <= 0 ? 1 : (byte) r; 532 } 533 } 534 maxr = mr; 535 } 536 537 static void setApplet(Applet app) { 538 applet = app; 539 } 540 541 Atom(int Rl, int Gl, int Bl) { 542 this.Rl = Rl; 543 this.Gl = Gl; 544 this.Bl = Bl; 545 } 546 547 private int blend(int fg, int bg, float fgfactor) { 548 return (int) (bg + (fg - bg) * fgfactor); 549 } 550 551 private void Setup() { 552 balls = new Image[nBalls]; 553 byte red[] = new byte[256]; 554 red[0] = (byte) bgGrey; 555 byte green[] = new byte[256]; 556 green[0] = (byte) bgGrey; 557 byte blue[] = new byte[256]; 558 blue[0] = (byte) bgGrey; 559 for (int r = 0; r < nBalls; r++) { 560 float b = (float) (r + 1) / nBalls; 561 for (int i = maxr; i >= 1; --i) { 562 float d = (float) i / maxr; 563 red[i] = (byte) blend(blend(Rl, 255, d), bgGrey, b); 564 green[i] = (byte) blend(blend(Gl, 255, d), bgGrey, b); 565 blue[i] = (byte) blend(blend(Bl, 255, d), bgGrey, b); 566 } 567 IndexColorModel model = new IndexColorModel(8, maxr + 1, 568 red, green, blue, 0); 569 balls[r] = applet.createImage( 570 new MemoryImageSource(R * 2, R * 2, model, data, 0, R * 2)); 571 } 572 } 573 574 void paint(Graphics gc, int x, int y, int r) { 575 Image ba[] = balls; 576 if (ba == null) { 577 Setup(); 578 ba = balls; 579 } 580 Image i = ba[r]; 581 int size = 10 + r; 582 gc.drawImage(i, x - (size >> 1), y - (size >> 1), size, size, applet); 583 } 584} 585