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 }