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.Image; |
|
44 import java.awt.Graphics; |
|
45 import java.awt.Dimension; |
|
46 import java.awt.event.MouseEvent; |
|
47 import java.awt.event.MouseListener; |
|
48 import java.awt.event.MouseMotionListener; |
|
49 import java.net.URL; |
|
50 import java.awt.image.IndexColorModel; |
|
51 import java.awt.image.MemoryImageSource; |
|
52 import java.io.BufferedReader; |
|
53 import java.io.IOException; |
|
54 import java.io.InputStream; |
|
55 import java.io.InputStreamReader; |
|
56 import java.io.StreamTokenizer; |
|
57 import java.util.HashMap; |
|
58 import java.util.Map; |
|
59 import java.util.logging.Level; |
|
60 import 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 */ |
|
68 final 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") |
|
297 public 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 |
|
503 class 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 } |
|