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