Back to index

wims  3.65+svn20090927
LatticeViewer.java
Go to the documentation of this file.
00001 /*
00002  * LatticeViewer.java       
00003  * Date :       18th Feb 1998      
00004  * Adapted by : Simon P.A.Gill
00005  * WWW :      http://www.le.ac.uk/engineering/spg3/lattice/
00006  * 
00007  * The following java code is an adaptation of the Molecule Viewer XYZApp.java
00008  * freely distributed by Sun Microsystems. The conditions stated below apply
00009  * to this code. S.P.A. Gill takes no responsibility for this code. 
00010  * Requires class Matrix3D.java (also from Sun at
00011  * http://www.javasoft.com:80/applets/js-applets.html)
00012  *
00013  *****************************************************************************
00014  *
00015  * @(#)XYZApp.java   1.3 96/12/06
00016  *
00017  * Copyright (c) 1994-1996 Sun Microsystems, Inc. All Rights Reserved.
00018  *
00019  * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
00020  * modify and redistribute this software in source and binary code form,
00021  * provided that i) this copyright notice and license appear on all copies of
00022  * the software; and ii) Licensee does not utilize the software in a manner
00023  * which is disparaging to Sun.
00024  *
00025  * This software is provided "AS IS," without a warranty of any kind. ALL
00026  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
00027  * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
00028  * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
00029  * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
00030  * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
00031  * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
00032  * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
00033  * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
00034  * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
00035  * POSSIBILITY OF SUCH DAMAGES.
00036  *
00037  * This software is not designed or intended for use in on-line control of
00038  * aircraft, air traffic, aircraft navigation or aircraft communications; or in
00039  * the design, construction, operation or maintenance of any nuclear
00040  * facility. Licensee represents and warrants that it will not use or
00041  * redistribute the Software for such purposes.
00042  */
00043 
00044 /*
00045  * Initially a set of classes to parse, represent and display Chemical compounds
00046  * in .xyz format (see http://chem.leeds.ac.uk/Project/MIME.html). Now adapted
00047  * to also display interatomic bonds, allow the user to define the atomic radii
00048  * and colour of the atoms and define other lines in the display (such as the
00049  * unit cell). This code will still display .xyz files but all the atoms will
00050  * look the same (the default atom size and colour) unless they are defined
00051  * in the .xyz file. A .xyz file in which the bond locations and atom colours and
00052  * sizes have been defined have been distinguished by the .lat extension
00053  *
00054  * FILE FORMAT :
00055  *
00056  * The file format is based around the standard .xyz file which defines the 
00057  * position in space of the atoms in the lattice
00058  * (see http://cst-www.nrl.navy.mil/lattice/ for details and many examples).
00059  * Any comments must be removed. Each atom type must be defined. 
00060  * The following line defines an atom by its name (C), its RGB colour (red=255,
00061  * green=0, blue=0) and relative size (default=1.0, minimum=0.0 maximum=2.0)
00062  *
00063  *     ATOM  C  255  0  0  1.0 
00064  *
00065  * The position and bonds of the atom are defined as follows :
00066  * 
00067  *     C  0.0  1.0  0.0  3  6  8
00068  *
00069  * This places a carbon (C) atom at the co-ordinates (0.0,1.0,0.0) and says that
00070  * bonds exist between it and the 3rd, 6th and 8th atoms in the list of atoms.
00071  * An atom can have an unlimited number of bonds, the only restriction is that
00072  * the total is less than 300 (unless the array size of bonds[] is increased.
00073  * NOTE - any atom defined with a size of 0.0 (eg ATOM X 0 0 0 0.0) can be used
00074  * to define the location of a position in space without an atom being placed
00075  * there. Any bonds between such points are known as the BOX (as these have
00076  * been used here to define the unit cell of a lattice) and are only displayed
00077  * if parameter box has value=true (see below).
00078  *
00079  * APPLET OPTIONS :
00080  *
00081  * PARAM = model     VALUE = filename
00082  * PARAM = label     VALUE = true/false   - labels atoms with their
00083  *                                          numerical position in the list.
00084  *                                          Very useful when defining bond
00085  *                                          positions (default = false)
00086  * PARAM = scale     VALUE = float        - size of lattice in applet (default=1.0)
00087  * PARAM = box              VALUE = true/false   - the box concept is defined above.
00088  *                                          Only displayed if TRUE (default=false)
00089  * PARAM = bonds     VALUE = true/false   - display interatomic bonds if true (default=false).
00090  * PARAM = bgcolor   VALUE = RGB colour   - background colour (default=lightGrey)
00091  * PARAM = bondcolor VALUE = RGB colour   - bond colour (default=black)
00092  * PARAM = boxcolor  VALUE = RGB colour   - colour of box bonds (default=red)
00093  * 
00094  * NOTE - it has been found that this applet runs significantly better under Sun's 
00095  * appletviewer than on Netscape (especially when viewing complex structures)
00096  */
00097 
00098 import java.applet.Applet;
00099 import java.awt.Image;
00100 import java.awt.Event;
00101 import java.awt.Graphics;
00102 import java.awt.Dimension;
00103 import java.awt.Color;
00104 import java.io.StreamTokenizer;
00105 import java.io.InputStream;
00106 import java.io.BufferedInputStream;
00107 import java.io.IOException;
00108 import java.net.URL;
00109 import java.util.Hashtable;
00110 import java.awt.image.IndexColorModel;
00111 import java.awt.image.ColorModel;
00112 import java.awt.image.MemoryImageSource;
00113 
00114 
00116 class XYZLatModel {
00117     float vert[];
00118     ScaleableAtom atoms[];
00119     int tvert[];
00120     int ZsortMap[];
00121     int nvert, maxvert;
00122     int bond[][] = new int[300][2];
00123     int nbond = -1;
00124     boolean box;
00125     boolean label;
00126     boolean bonds;
00127     int br, bg, bb;  // RGB of bonds
00128     int gr, gg, gb;  // RGB of background
00129     int xr, xg, xb;  // RGB of box
00130 
00131     static Hashtable atomTable;
00132     static ScaleableAtom defaultAtom;
00133 
00134     boolean transformed;
00135     Matrix3D mat;
00136 
00137     float xmin, xmax, ymin, ymax, zmin, zmax;
00138 
00139 
00140     XYZLatModel () {
00141        mat = new Matrix3D();
00142        mat.xrot(20);
00143        mat.yrot(30);
00144     }
00145 
00146 
00148     XYZLatModel (InputStream is, boolean labl, boolean bx, boolean bnds, Color bgcolor, Color bndcolor, Color bxcolor) throws Exception
00149     {
00150        this();
00151        StreamTokenizer st;
00152        st = new StreamTokenizer(new BufferedInputStream(is, 4000));
00153        st.eolIsSignificant(true);
00154        st.commentChar('#');
00155        int slot = 0;
00156       
00157        this.label=labl;
00158        this.box=bx;
00159        this.bonds=bnds;
00160        this.br=bndcolor.getRed();
00161        this.bg=bndcolor.getGreen();
00162        this.bb=bndcolor.getBlue();
00163        this.gr=bgcolor.getRed();
00164        this.gg=bgcolor.getGreen();
00165        this.gb=bgcolor.getBlue();
00166        this.xr=bxcolor.getRed();
00167        this.xg=bxcolor.getGreen();
00168        this.xb=bxcolor.getBlue();
00169        atomTable = new Hashtable();
00170        defaultAtom = new ScaleableAtom(255, 100, 200, 1.0);
00171        try
00172        {
00173 scan:
00174           while (true)
00175           {
00176              switch ( st.nextToken() ) 
00177              {
00178                 case StreamTokenizer.TT_EOF:
00179                    break scan;
00180                 default:
00181                    break;
00182                 case StreamTokenizer.TT_WORD:
00183                    // get atom type
00184                    String name = st.sval;
00185                    if (name.equals("ATOM")) {
00186                        // get name, RGB colour and size
00187                        int r=255, g=255, b=255;
00188                        double s=1.0;
00189                        if (st.nextToken() == StreamTokenizer.TT_WORD) 
00190                        {
00191                            name=st.sval;
00192                            if (st.nextToken() == StreamTokenizer.TT_NUMBER) 
00193                            {
00194                               r = (int)st.nval;
00195                               if (st.nextToken() == StreamTokenizer.TT_NUMBER) 
00196                               {
00197                                  g = (int)st.nval;
00198                                  if (st.nextToken() == StreamTokenizer.TT_NUMBER)
00199                                  {
00200                                      b =(int)st.nval;
00201                                      if (st.nextToken() == StreamTokenizer.TT_NUMBER)
00202                                          s = (double)st.nval;
00203                                  }
00204                               }
00205                            }
00206                        }
00207                        if (s>=2.0) s=2.0;
00208                        if (s<0.0) s=1.0;
00209                       atomTable.put(name.toLowerCase(), new ScaleableAtom(r, g, b, s));
00210                    } else {
00211                        // get position
00212                        double x = 0, y = 0, z = 0;
00213                        if (st.nextToken() == StreamTokenizer.TT_NUMBER) 
00214                        {
00215                           x = st.nval;
00216                           if (st.nextToken() == StreamTokenizer.TT_NUMBER) 
00217                           {
00218                              y = st.nval;
00219                              if (st.nextToken() == StreamTokenizer.TT_NUMBER)
00220                                 z = st.nval;
00221                           }
00222                        }
00223                        addVert(name, (float) x, (float) y, (float) z);
00224                        // get connectivities
00225                        while (st.nextToken() == StreamTokenizer.TT_NUMBER)
00226                        {
00227                          nbond++;
00228                          bond[nbond][0]=nvert;
00229                          bond[nbond][1]=(int)st.nval;
00230                        }
00231                    }
00232                    while( st.ttype != StreamTokenizer.TT_EOL &&
00233                           st.ttype != StreamTokenizer.TT_EOF )
00234                    st.nextToken();
00235 
00236              }   // end Switch
00237 
00238           }  // end while
00239 
00240           is.close();
00241 
00242        }  // end Try
00243        catch( IOException e) {}
00244 
00245        if (st.ttype != StreamTokenizer.TT_EOF)
00246           throw new Exception(st.toString());
00247 
00248     }  // end XYZLatModel()
00249 
00250 
00252     int addVert(String name, float x, float y, float z) {
00253        int i = nvert;
00254        if (i >= maxvert)
00255            if (vert == null) {
00256               maxvert = 100;
00257               vert = new float[maxvert * 3];
00258               atoms = new ScaleableAtom[maxvert];
00259            } else {
00260               maxvert *= 2;
00261               float nv[] = new float[maxvert * 3];
00262               System.arraycopy(vert, 0, nv, 0, vert.length);
00263               vert = nv;
00264               ScaleableAtom na[] = new ScaleableAtom[maxvert];
00265               System.arraycopy(atoms, 0, na, 0, atoms.length);
00266               atoms = na;
00267            }
00268        ScaleableAtom a = (ScaleableAtom) atomTable.get(name.toLowerCase());
00269        if (a == null) a = defaultAtom;
00270        atoms[i] = a;
00271        i *= 3;
00272        vert[i] = x;
00273        vert[i + 1] = y;
00274        vert[i + 2] = z;
00275        return nvert++;
00276     }
00277 
00279     void transform() {
00280        if (transformed || nvert <= 0)
00281            return;
00282        if (tvert == null || tvert.length < nvert * 3)
00283            tvert = new int[nvert * 3];
00284        mat.transform(vert, tvert, nvert);
00285        transformed = true;
00286     }
00287 
00288 
00293     void paint(Graphics g) {
00294        if (vert == null || nvert <= 0)
00295            return;
00296        transform();
00297        int v[] = tvert;
00298        int zs[] = ZsortMap;
00299        if (zs == null) {
00300            ZsortMap = zs = new int[nvert];
00301            for (int i = nvert; --i >= 0;)
00302               zs[i] = i * 3;
00303        }
00304 
00305        /*
00306         * I use a bubble sort since from one iteration to the next, the sort
00307         * order is pretty stable, so I just use what I had last time as a
00308         * "guess" of the sorted order.  With luck, this reduces O(N log N)
00309         * to O(N)
00310         */
00311 
00312        for (int i = nvert - 1; --i >= 0;) {
00313            boolean flipped = false;
00314            for (int j = 0; j <= i; j++) {
00315               int a = zs[j];
00316               int b = zs[j + 1];
00317               if (v[a + 2] > v[b + 2]) {
00318                   zs[j + 1] = a;
00319                   zs[j] = b;
00320                   flipped = true;
00321               }
00322            }
00323            if (!flipped)
00324               break;
00325        }
00326 
00327        int lg = 0;
00328        int lim = nvert;
00329        ScaleableAtom ls[] = atoms;
00330         int drawn[] = new int[nbond+1];
00331        if (lim <= 0 || nvert <= 0)
00332            return;
00333        for (int i = 0; i < lim; i++) {
00334            int j = zs[i];
00335            int grey = v[j + 2];
00336            if (grey < 0)
00337               grey = 0;
00338            if (grey > 15)
00339               grey = 15;
00340 
00341             // draw bonds
00342             int v1, v2;
00343             boolean e = atoms[j/3].Exist();
00344             v1=j/3+1;
00345             if (((e)&&(bonds))||((!e)&&(box)))
00346             {
00347                 for (int k=0; k<=nbond; k++)
00348                 {
00349                     v2=-1;
00350                     if (bond[k][0]==v1)
00351                         v2=bond[k][1]-1;
00352                     if (bond[k][1]==v1)
00353                         v2=bond[k][0]-1;
00354                     if ((v2!=-1)&&(drawn[k]==0))
00355                     {
00356                         drawn[k]=1;
00357                         double rr=(v[j+2]+v[v2*3+2]+6)/36.0;
00358                         int r1,b1,g1;
00359                         if (e) {
00360                             r1=(int)(rr*(br-gr)+gr);
00361                             b1=(int)(rr*(bb-gb)+gb);
00362                             g1=(int)(rr*(bg-gg)+gg);
00363                         } else {
00364                             r1=(int)(rr*(xr-gr)+gr);
00365                             b1=(int)(rr*(xb-gb)+gb);
00366                             g1=(int)(rr*(xg-gg)+gg);
00367                         }   
00368                         Color line = new Color(r1,g1,b1);
00369                         g.setColor(line);
00370                         g.drawLine(v[j],v[j+1],v[v2*3],v[v2*3+1]);
00371                     }
00372                 }
00373             }
00374             if (e)
00375                atoms[j/3].paint(g, v[j], v[j + 1], grey);
00376             if (label) {
00377                 g.setColor(Color.black);
00378                g.drawString(String.valueOf(v1), v[j], v[j+1]); 
00379             }
00380        }
00381     }
00382 
00384     void findBB() {
00385        if (nvert <= 0)
00386            return;
00387        float v[] = vert;
00388        float xmin = v[0], xmax = xmin;
00389        float ymin = v[1], ymax = ymin;
00390        float zmin = v[2], zmax = zmin;
00391        for (int i = nvert * 3; (i -= 3) > 0;) {
00392            float x = v[i];
00393            if (x < xmin)
00394               xmin = x;
00395            if (x > xmax)
00396               xmax = x;
00397            float y = v[i + 1];
00398            if (y < ymin)
00399               ymin = y;
00400            if (y > ymax)
00401               ymax = y;
00402            float z = v[i + 2];
00403            if (z < zmin)
00404               zmin = z;
00405            if (z > zmax)
00406               zmax = z;
00407        }
00408        this.xmax = xmax;
00409        this.xmin = xmin;
00410        this.ymax = ymax;
00411        this.ymin = ymin;
00412        this.zmax = zmax;
00413        this.zmin = zmin;
00414     }
00415 }
00416 
00418 public class LatticeViewer extends Applet implements Runnable {
00419     XYZLatModel md;
00420     boolean painted = true;
00421     float xfac;
00422     int prevx, prevy;
00423     float xtheta, ytheta;
00424     float scalefudge = 1;
00425     Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
00426     String mdname = null;
00427     String message = null;
00428     boolean label = false;
00429     boolean box = false;
00430     boolean bonds = false;
00431     Image backBuffer;
00432     Graphics backGC;
00433     Dimension backSize;
00434     String param;
00435     Color bgcolor, bondcolor, boxcolor;
00436 
00437 
00438     private synchronized void newBackBuffer() {
00439        backBuffer = createImage(size().width, size().height);
00440        backGC = backBuffer.getGraphics();
00441        backSize = size();
00442     }
00443 
00444     public void init() {
00445        mdname = getParameter("model");
00446        try {
00447            scalefudge = Float.valueOf(getParameter("scale")).floatValue();
00448        } catch(Exception e) {
00449        };
00450        try {
00451            label = Boolean.valueOf(getParameter("label")).booleanValue();
00452        } catch(Exception e) {
00453        };
00454        try {
00455            box = Boolean.valueOf(getParameter("box")).booleanValue();
00456        } catch(Exception e) {
00457        };
00458        try {
00459            bonds = Boolean.valueOf(getParameter("bonds")).booleanValue();
00460        } catch(Exception e) {
00461        };
00462        try {
00463            param = getParameter("bgcolor");
00464        } catch(Exception e) {
00465        };
00466         try {
00467           Integer i = Integer.valueOf(param, 16);
00468           bgcolor = new Color(i.intValue());
00469         } catch (NumberFormatException e) {
00470           bgcolor = Color.lightGray;
00471         }
00472        try {
00473            param = getParameter("bondcolor");
00474        } catch(Exception e) {
00475        };
00476         try {
00477           Integer i = Integer.valueOf(param, 16);
00478           bondcolor = new Color(i.intValue());
00479         } catch (NumberFormatException e) {
00480           bondcolor = Color.black;
00481         };
00482        try {
00483            param = getParameter("boxcolor");
00484        } catch(Exception e) {
00485        };
00486         try {
00487           Integer i = Integer.valueOf(param, 16);
00488           boxcolor = new Color(i.intValue());
00489         } catch (NumberFormatException e) {
00490           boxcolor = Color.red;
00491         };
00492        amat.yrot(20);
00493        amat.xrot(20);
00494        if (mdname == null)
00495            mdname = "model.obj";
00496        resize(size().width <= 20 ? 400 : size().width,
00497               size().height <= 20 ? 400 : size().height);
00498        newBackBuffer();
00499     }
00500     public void run() {
00501        InputStream is = null;
00502        try {
00503            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
00504            is = new URL(getDocumentBase(), mdname).openStream();
00505            XYZLatModel m = new XYZLatModel (is, label, box, bonds, bgcolor, bondcolor, boxcolor);
00506            ScaleableAtom.setApplet(this);
00507            md = m;
00508            m.findBB();
00509            float xw = m.xmax - m.xmin;
00510            float yw = m.ymax - m.ymin;
00511            float zw = m.zmax - m.zmin;
00512            if (yw > xw)
00513               xw = yw;
00514            if (zw > xw)
00515               xw = zw;
00516            float f1 = size().width / xw;
00517            float f2 = size().height / xw;
00518            xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
00519        } catch(Exception e) {
00520            e.printStackTrace();
00521            md = null;
00522            message = e.toString();
00523        }
00524        try {
00525            if (is != null)
00526               is.close();
00527        } catch(Exception e) {
00528        }
00529        repaint();
00530     }
00531     public void start() {
00532        if (md == null && message == null)
00533            new Thread(this).start();
00534     }
00535     public void stop() {
00536     }
00537     public boolean mouseDown(Event e, int x, int y) {
00538        prevx = x;
00539        prevy = y;
00540        return true;
00541     }
00542     public boolean mouseDrag(Event e, int x, int y) {
00543        tmat.unit();
00544        float xtheta = (prevy - y) * (360.0f / size().width);
00545        float ytheta = (x - prevx) * (360.0f / size().height);
00546        tmat.xrot(xtheta);
00547        tmat.yrot(ytheta);
00548        amat.mult(tmat);
00549        if (painted) {
00550            painted = false;
00551            repaint();
00552        }
00553        prevx = x;
00554        prevy = y;
00555        return true;
00556     }
00557     public void update(Graphics g) {
00558        if (backBuffer == null)
00559            g.clearRect(0, 0, size().width, size().height);
00560        paint(g);
00561     }
00562 
00563     public void paint(Graphics g) {
00564        if (md != null) {
00565            md.mat.unit();
00566            md.mat.translate(-(md.xmin + md.xmax) / 2,
00567                           -(md.ymin + md.ymax) / 2,
00568                           -(md.zmin + md.zmax) / 2);
00569            md.mat.mult(amat);
00570            // md.mat.scale(xfac, -xfac, 8 * xfac / size().width);
00571            md.mat.scale(xfac, -xfac, 16 * xfac / size().width);
00572            md.mat.translate(size().width / 2, size().height / 2, 8);
00573            md.transformed = false;
00574            if (backBuffer != null) {
00575               if (!backSize.equals(size()))
00576                   newBackBuffer();
00577                 setBackground(bgcolor);
00578               backGC.setColor(bgcolor);
00579               backGC.fillRect(0,0,size().width,size().height);
00580               md.paint(backGC);
00581               g.drawImage(backBuffer, 0, 0, this);
00582            }
00583            else
00584               md.paint(g);
00585            setPainted();
00586        } else if (message != null) {
00587            g.drawString("Error in model:", 3, 20);
00588            g.drawString(message, 10, 40);
00589        }
00590     }
00591     private synchronized void setPainted() {
00592        painted = true;
00593        notifyAll();
00594     }
00595 
00596     private synchronized void waitPainted() 
00597     {
00598        while (!painted)
00599        {
00600           try
00601           {
00602              wait();
00603           }
00604           catch (InterruptedException e) {}
00605        }
00606        painted = false;
00607     }
00608 }   // end class LatticeViewer
00609 
00610 class ScaleableAtom {
00611     private static Applet applet;
00612     private static byte[] data;
00613     private final static int R = 40;
00614     private final static int hx = 15;
00615     private final static int hy = 15;
00616     private final static int bgGrey = 192;
00617     private static int maxr;
00618 
00619     private int Rl;
00620     private int Gl;
00621     private int Bl;
00622     private double Sf;
00623 
00624     private Image balls[];
00625 
00626     static {
00627        data = new byte[R * 2 * R * 2];
00628        int mr = 0;
00629        for (int Y = 2 * R; --Y >= 0;) {
00630            int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5);
00631            int p = Y * (R * 2) + R - x0;
00632            for (int X = -x0; X < x0; X++) {
00633               int x = X + hx;
00634               int y = Y - R + hy;
00635               int r = (int) (Math.sqrt(x * x + y * y) + 0.5);
00636               if (r > mr)
00637                   mr = r;
00638               data[p++] = r <= 0 ? 1 : (byte) r;
00639            }
00640        }
00641        maxr = mr;
00642     }
00643     static void setApplet(Applet app) {
00644        applet = app;
00645     }
00646     ScaleableAtom(int Rl, int Gl, int Bl, double Sf) {
00647        this.Rl = Rl;
00648        this.Gl = Gl;
00649        this.Bl = Bl;
00650         this.Sf = Sf;
00651     }
00652     boolean Exist() {
00653        if (Sf==0.0)
00654             return false;
00655         else
00656             return true;
00657     }
00658     private final int blend(int fg, int bg, float fgfactor) {
00659        return (int) (bg + (fg - bg) * fgfactor);
00660     }
00661     private void Setup(int nBalls) {
00662        balls = new Image[nBalls];
00663        byte red[] = new byte[256];
00664        red[0] = (byte) bgGrey;
00665        byte green[] = new byte[256];
00666        green[0] = (byte) bgGrey;
00667        byte blue[] = new byte[256];
00668        blue[0] = (byte) bgGrey;
00669        for (int r = 0; r < nBalls; r++) {
00670            float b = (float) (r+1) / nBalls;
00671            for (int i = maxr; i >= 1; --i) {
00672               float d = (float) i / maxr;
00673               red[i] = (byte) blend(blend(Rl, 255, d), bgGrey, b);
00674               green[i] = (byte) blend(blend(Gl, 255, d), bgGrey, b);
00675               blue[i] = (byte) blend(blend(Bl, 255, d), bgGrey, b);
00676            }
00677            IndexColorModel model = new IndexColorModel(8, maxr + 1,
00678                                                  red, green, blue, 0);
00679            balls[r] = applet.createImage(
00680               new MemoryImageSource(R*2, R*2, model, data, 0, R*2));
00681        }
00682     }
00683     void paint(Graphics gc, int x, int y, int r) {
00684        Image ba[] = balls;
00685        if (ba == null) {
00686            Setup((int)(16*Sf));
00687            ba = balls;
00688        }
00689         r=(int)(r*Sf);
00690        Image i = ba[r];
00691        int size = 10 + r;
00692        gc.drawImage(i, x - (size >> 1), y - (size >> 1), size, size, applet);
00693     }
00694 }