/* IdleLife simulation: dt varies with max G0 Animated display of populations 11 april 1998 Chris Davis - */ import java.awt.*; import java.lang.*; import java.applet.*; import java.util.*; import java.net.*; public class IdleLife extends Applet implements Runnable { double elapsed_time = 0; double dt = 1.0; // actual time step double dt_requ = 8.0; // required time step double display_dt = 256.0; // display time step static int display_option = 0; // 0 display refreshed Pool pool; SpaceCanvas canvas1; int frameNumber = -1; Thread animatorThread = null; boolean frozen = true; Label counter; Button b1; Checkbox checkbox1; int predPARAM; public String getAppletInfo() { return "Species Island demo.\r\n"; } public void init() { //Body pool pool = new Pool( this ); // get configuration data try { predPARAM = Integer.parseInt( getParameter("PREDATION") ); } catch( Exception e ) { predPARAM = 0; } // set up the GUI GridBagLayout gridBag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(gridBag); b1 = new Button(" Start/Stop "); c.gridwidth = GridBagConstraints.EAST; c.gridx = 0; c.gridy = 0; c.gridwidth = 1; c.gridheight= 1; c.weightx = 1.0; c.weighty = 0.0; gridBag.setConstraints(b1, c); add(b1); canvas1 = new SpaceCanvas(pool); c.fill = GridBagConstraints.BOTH; c.gridx = 0; c.gridy = 1; c.gridwidth = 3; c.gridheight= 1; c.weightx = 1.0; c.weighty = 1.0; gridBag.setConstraints(canvas1, c); add(canvas1); validate(); pool.init(); } public void bodyCount( int n ) { counter.setText( n + " Bodies" ); } public void start() { if (animatorThread == null) { animatorThread = new Thread(this); animatorThread.start(); } } public void stop() { if ( animatorThread != null ) { animatorThread.stop(); animatorThread = null; } } public void destroy() { } public boolean action(Event e, Object arg) { // start/stop button if ( e.target == b1 ) { frozen = !frozen; } return true; } public boolean mouseDown(Event e, int x, int y) { return true; } public void run() { double time, displayTime = 0; Thread.currentThread().setPriority(Thread.MIN_PRIORITY); //This is the animation loop. while (Thread.currentThread() == animatorThread) { try { if ( !frozen ) { //Advance the animation frame. frameNumber++; // work out species idleness time = pool.engine(); if ( time > displayTime ) { canvas1.redraw(); displayTime = time + 0.1; } animatorThread.sleep(30); } else { animatorThread.sleep(500); } } catch ( InterruptedException e ) { stop(); } } } } // Idle Life Form class Lifeform { int index; // index number double ipop; // initial population double fpop; // final population double Pm; // metabolic rate double Pr; // reproduction power double Pe; // acquisition power double Er; // energy of reproduction double G0; // zero idleness gain, G // indexed constructor sets Pm as index- and sine-dependent value public Lifeform( double ipr, double ipe, double ier, int index, double dth, double h, double r, double g, double pop) { this.index = index; // index is used to generate this.Pm with sinewave of amplitude r, // mean value h, rising with index * g. This means that if g // is negative, Pm gradually falls with index. this.Pm = h + r * Math.sin((double)index * dth) + (double)index * g; if ( this.Pm <= 0 ) this.Pm = 0.001; // this.Pr = ipr + .8 * ipr * Math.cos( (double)index * dth ); this.Pr = ipr + (double)index * g; this.Pe = ipe; this.Er = ier; // environmental gain G at zero idleness this.G0 = ( ( this.Pm + this.Pr ) / this.Pe ) + 1; this.ipop = pop; this.fpop = 0; } // calculate Idleness given environmental gain G. public double idleness( double gain ) { double i; i = 1 - ((this.Pm + this.Pr)/(this.Pe *( gain - 1.0 ))); if ( i < 0 ) i = 0; if ( i > 1 ) i = 0; return( i ); } // calculate power consumption public double poweruse( double i, double gain ) { return ( ( 1 - i ) * gain * this.Pe ); } // calculate new subpopulation after time t public double growPopulation( int nsubpop, double i, double t ) { double pop, np; np = (double)nsubpop; pop = this.ipop / np; if ( i > 0 ) { pop = pop * Math.pow((1.0 + this.Pr / this.Er), t ); } else { pop = 0; } return( pop ); } } // Pool class variables, constructors and methods // A pool gains energy at some rate, and contains // a population of creatures which consume energy. class Pool { Hashtable ht; IdleLife thisApplet; double time; double gain; double powerin; double poolenergy, lastpoolenergy; double maxG0, minG0; int minG0index = -1; double poolsize; final int nps = 40; // number of distinct populations final int nsubpop = 9; // no. of subpopulations must be odd number! int indexedPop[] = new int[ nps ]; // conveniently indexed pop array int predation; // % predation double dt; // Constructor allows access to Applet public Pool( IdleLife a ) { super(); thisApplet = a; } public void init() { int i; predation = thisApplet.predPARAM; // % predation ht = new Hashtable(); for ( i = 0; i < nps; i++ ) { ht.put( new Integer(i), new Lifeform( 2.0, 10.0, 20.0, i, Math.PI/3, 10.0, 0.5, -0.01, 1.0) ); System.out.println( i + " " + Math.sin((double)i * Math.PI/3) ); } poolenergy = 10000.0; lastpoolenergy = poolenergy; maxG0 = 0; poolsize = 500.0; powerin = 2000.0; time = 0; dt = 0.25; } // main engine calculates new populations, pool energy public double engine() { Lifeform f; Integer j = new Integer(0); double powerout; double fd, i; double g0, g1, dg; int npop = 0; double fdmin = 0.6; double fdinc = ( 1.0 - fdmin ) / ( ( (double)nsubpop-1 )/2.0 ); System.out.print( time + " " ); powerout = 0.0; if ( !ht.isEmpty() ) { // dt control increases dt when poolenergy G is rising, // and reduces dt when falling and threatening extinction. // Using gain in subpop 0 ( the lowest ), // change dt, increasing if gain is rising. // Using maxG0, the maximum G0 value of the different populations, // keep halving dt until fall in gain over dt keeps it above maxG0. g0 = fdmin * lastpoolenergy / poolsize; // last gain G g1 = fdmin * poolenergy / poolsize; // current gain G dg = g1 - g0; // change in gain if ( dg > 0 ) { // increase dt to a maximum dt = dt * 2; // double dt if ( dt > 1.0 ) dt = 1.0; // ..to a maximum } else { // decrease dt until project gain > maximum population G0 // or until dt too small if ( ((g1 + dg) < maxG0) && ( dt > .01 ) ) { boolean toobigdt = true; while ( toobigdt ) { if ( (g1 + dg) < maxG0 ) { dg = dg/2; dt = dt/2; } else toobigdt = false; if ( dt < .01 ) toobigdt = false; // too small } } } // go through each subpopulation whose locality has a different gain G // finding idleness, power consumption, new population. for ( int subpop = 0; subpop < nsubpop; subpop++ ) { // The total population of each life form is distributed // evenly in the pool. // Each subpopulation inhabits a part of the pool with // a higher or lower energy density than average. fd = fdmin + (double)subpop * fdinc; gain = fd * poolenergy / poolsize; // find subpopulation idleness, power consumption, population growth npop = 0; for ( Enumeration e = ht.elements(); e.hasMoreElements(); ) { npop++; f = (Lifeform)e.nextElement(); i = f.idleness( gain ); if ( i > 0 ) { powerout += f.poweruse( i, gain ) * f.ipop/(double)nsubpop; f.fpop += f.growPopulation( nsubpop, i, dt ); } } } System.out.print( npop + "/" ); // work out total pool energy, and pool energy density lastpoolenergy = poolenergy; poolenergy += (powerin - powerout) * dt; if ( poolenergy > 30000 ) poolenergy = 30000; if ( poolenergy <= 0 ) { poolenergy = 0; } // copy final populations to initial populations, // and remove zero population lifeforms from hashtable. // Also find maximum G0 value in populations maxG0 = 0; minG0 = 1000; double fpred = 1.0 - dt * (double)predation / 100.0; System.out.print( fpred + " " ); for ( Enumeration e = ht.elements(); e.hasMoreElements(); ) { f = (Lifeform)e.nextElement(); f.ipop = fpred * f.fpop; f.fpop = 0.0; if ( f.ipop <= 0.1 ) { // System.out.println(f.index + " extinct," ); j = new Integer( f.index ); ht.remove( j ); indexedPop[f.index] = 0; } else { if ( f.G0 > maxG0 ) { maxG0 = f.G0; } if ( f.G0 < minG0 ) { minG0 = f.G0; minG0index = f.index; } indexedPop[f.index] = (int)f.ipop + 1; } } for ( int n = 0; n < nps; n++ ) { } } time = time + dt; System.out.println( minG0index ); return( time ); } } class SpaceCanvas extends Canvas { Dimension offDimension; Image offImage; Graphics offGraphics; Pool pl; // Constructor allows access to Pool objects by this class public SpaceCanvas( Pool pool) { super(); pl = pool; this.setBackground( Color.black ); } public void stop() { offGraphics = null; offImage = null; } public boolean handleEvent( Event evt ) { return true; } public void redraw() { repaint(); } // update calls paint(), otherwise clears the screen. public void update(Graphics g) { paint(g); } public void paint(Graphics g) { Dimension d = size(); //Create the offscreen graphics context, if no good one exists. if ( (offGraphics == null) || (d.width != offDimension.width) || (d.height != offDimension.height) ) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); System.out.println( "Graphics setup" ); } backpaint( offGraphics ); //Paint the image onto the screen. g.drawImage(offImage, 0, 0, this); } public void backpaint(Graphics offGraphics) { int x1, y1, x2, y2; int m, n; int gx0, gy0; Dimension d = size(); offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, d.width, d.height); // display idleness offGraphics.setPaintMode(); offGraphics.setColor( Color.red ); gx0 = 25; gy0 = 210; for ( n = 0; n < pl.nps - 1; n++ ){ x1 = gx0 + 5 * n; y1 = gy0 - pl.indexedPop[n]; x2 = gx0 + 5 * (n+1); y2 = gy0 - pl.indexedPop[n+1]; offGraphics.drawLine( x1, y1, x2, y2 ); } // draw min G0 offGraphics.setColor( Color.gray ); int mgi = gx0 + pl.minG0index * 5; offGraphics.drawLine( mgi , gy0 - 200, mgi, gy0 ); VecFont.drawString(offGraphics, "min G0 pop " , gx0 - 5, gy0 - 25, 1.0, 90.0); // draw axes and labels offGraphics.setColor( Color.white ); offGraphics.drawLine( gx0, gy0, gx0+ 200, gy0 ); offGraphics.drawLine( gx0, gy0 - 200, gx0, gy0 ); VecFont.drawString(offGraphics, "Population", gx0 - 5, gy0 - 20, 1.0, 90.0); VecFont.drawString(offGraphics, "Varieties", gx0 +20, gy0 + 10, 1.0, 0.0); } } class VecFont { /* graphics character set ( 5 wide x 10 high ) Each row begins with a vector count N , followed by N 2-byte vectors. Each vector is stored with X in high nibble (msb not used), Y in low nibble, 0x04 is location of bottom left of character on line. 0x4A is top rightmost */ private final static int MAXASCENT = 6; private final static int MAXDESCENT = 3; private final static int LEADING = 1; // space between rows private final static int MAXADVANCE = 6; // width of chars including gap private final static int CHARWIDTH = 5; // (fixed) char width private final static int MAXCHAR = 127; private final static byte gchars[][] = { {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {4,0x12,0x04,0x16,0x04,0x04,0x44,0x44,0x49}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {1,0x32,0x32}, {0}, // space {2,0x24,0x24,0x26,0x2a}, // ! {2,0x1a,0x18,0x3a,0x38}, // " {4,0x14,0x18,0x34,0x38,0x07,0x47,0x05,0x45}, // # {6,0x05,0x45,0x45,0x47,0x47,0x07,0x07,0x09,0x09,0x49,0x2a,0x24}, // $ {11,0x04,0x05,0x05,0x49,0x49,0x4a,0x0a,0x08,0x08,0x28,0x28,0x2a,0x2a,0x0a,0x24,0x44,0x44,0x46,0x46,0x26,0x26,0x24}, // % {8,0x44,0x08,0x08,0x0a,0x0a,0x3a,0x3a,0x39,0x39,0x06,0x06,0x04,0x04,0x24,0x24,0x46}, // & {2,0x18,0x29,0x29,0x3a}, // ' {3,0x34,0x16,0x16,0x18,0x18,0x3a}, // ( {3,0x14,0x36,0x36,0x38,0x38,0x1a}, // ) {4,0x06,0x46,0x24,0x28,0x04,0x48,0x08,0x44}, // * {2,0x24,0x28,0x06,0x46}, // + {2,0x25,0x24,0x13,0x24}, // ' {2,0x16,0x26,0x26,0x36}, // - {2,0x24,0x24,0x24,0x24}, // . {3,0x04,0x05,0x05,0x49,0x49,0x4a}, // / {4,0x04,0x0a,0x0a,0x4a,0x4a,0x44,0x44,0x04}, // 0 {2,0x24,0x2a,0x2a,0x19}, // 1 {5,0x44,0x04,0x04,0x07,0x07,0x47,0x47,0x4a,0x4a,0x0a}, // 2 {4,0x04,0x44,0x44,0x4a,0x4a,0x0a,0x47,0x07}, // 3 {3,0x0a,0x07,0x07,0x47,0x34,0x3a}, // 4 {5,0x04,0x44,0x44,0x47,0x47,0x07,0x07,0x0a,0x0a,0x4a}, // 5 {5,0x4a,0x0a,0x0a,0x04,0x04,0x44,0x44,0x47,0x47,0x07}, // 6 {4,0x0a,0x4a,0x4a,0x49,0x49,0x16,0x16,0x14}, // 7 {9,0x04,0x07,0x07,0x47,0x47,0x44,0x44,0x04,0x27,0x09,0x09,0x0a,0x0a,0x4a,0x4a,0x49,0x49,0x27}, // 8 {5,0x04,0x44,0x44,0x4a,0x4a,0x0a,0x0a,0x07,0x07,0x47}, // 9 {2,0x24,0x24,0x27,0x27}, // : {3,0x13,0x24,0x24,0x25,0x27,0x27}, // ; {2,0x34,0x16,0x16,0x38}, // < {2,0x05,0x45,0x07,0x47}, // = {2,0x14,0x36,0x36,0x18}, // > {6,0x24,0x24,0x25,0x47,0x47,0x49,0x49,0x3a,0x3a,0x1a,0x1a,0x09}, // ? {7,0x24,0x04,0x04,0x0a,0x0a,0x4a,0x4a,0x46,0x46,0x26,0x26,0x28,0x28,0x48}, // @ {4,0x04,0x0a,0x0a,0x4a,0x4a,0x44,0x47,0x07}, // A {8,0x04,0x0a,0x0a,0x3a,0x3a,0x38,0x38,0x27,0x07,0x37,0x37,0x46,0x46,0x44,0x44,0x04}, // B {5,0x4a,0x2a,0x2a,0x08,0x08,0x06,0x06,0x24,0x24,0x44}, // C {6,0x04,0x0a,0x0a,0x2a,0x2a,0x48,0x48,0x46,0x46,0x24,0x24,0x04}, // D {4,0x4a,0x0a,0x0a,0x04,0x04,0x44,0x07,0x27}, // E {3,0x04,0x0a,0x0a,0x4a,0x07,0x27}, // F {5,0x4a,0x0a,0x0a,0x04,0x04,0x44,0x44,0x46,0x46,0x26}, // G {3,0x04,0x0a,0x07,0x47,0x4a,0x44}, // H {3,0x14,0x34,0x24,0x2a,0x1a,0x3a}, // I {3,0x06,0x04,0x04,0x44,0x44,0x4a}, // J {4,0x0a,0x04,0x07,0x17,0x17,0x4a,0x17,0x44}, // K {2,0x0a,0x04,0x04,0x44}, // L {5,0x04,0x0a,0x0a,0x28,0x28,0x4a,0x4a,0x44,0x28,0x27}, // M {3,0x04,0x0a,0x09,0x45,0x4a,0x44}, // N {8,0x14,0x05,0x05,0x09,0x09,0x1a,0x1a,0x3a,0x3a,0x49,0x49,0x45,0x45,0x34,0x34,0x14}, // O {4,0x07,0x47,0x47,0x4a,0x4a,0x0a,0x0a,0x04}, // P {5,0x04,0x0a,0x0a,0x4a,0x4a,0x44,0x44,0x04,0x26,0x44}, // Q {5,0x04,0x0a,0x0a,0x4a,0x4a,0x47,0x47,0x07,0x17,0x44}, // R {5,0x04,0x44,0x44,0x47,0x47,0x07,0x07,0x0a,0x0a,0x4a}, // S {2,0x24,0x2a,0x0a,0x4a}, // T {3,0x0a,0x04,0x04,0x44,0x44,0x4a}, // U {4,0x0a,0x06,0x06,0x24,0x24,0x46,0x46,0x4a}, // V {5,0x0a,0x04,0x04,0x26,0x26,0x44,0x44,0x4a,0x26,0x27}, // W {6,0x04,0x05,0x05,0x49,0x49,0x4a,0x0a,0x09,0x09,0x45,0x45,0x44}, // X {5,0x0a,0x09,0x09,0x27,0x27,0x49,0x49,0x4a,0x27,0x24}, // Y {5,0x0a,0x4a,0x4a,0x49,0x49,0x05,0x05,0x04,0x04,0x44}, // Z {3,0x34,0x14,0x14,0x1a,0x1a,0x3a}, // [ {3,0x0a,0x09,0x09,0x45,0x45,0x44}, // \ {3,0x14,0x34,0x34,0x3a,0x3a,0x1a}, // ] {2,0x08,0x2a,0x2a,0x48}, // ^ {1,0x02,0x62}, // _ {1,0x37,0x29}, // ` {5,0x46,0x24,0x24,0x04,0x04,0x08,0x08,0x48,0x48,0x44}, // a {4,0x0a,0x04,0x04,0x44,0x44,0x48,0x48,0x08}, // b {3,0x48,0x08,0x08,0x04,0x04,0x44}, // c {4,0x48,0x08,0x08,0x04,0x04,0x44,0x44,0x4a}, // d {5,0x06,0x46,0x46,0x48,0x48,0x08,0x08,0x04,0x04,0x44}, // e {4,0x04,0x08,0x08,0x2a,0x2a,0x4a,0x07,0x37}, // f {5,0x44,0x04,0x04,0x08,0x08,0x48,0x48,0x42,0x42,0x02}, // g {3,0x04,0x0a,0x08,0x48,0x48,0x44}, // h {2,0x24,0x28,0x2a,0x2a}, // i {3,0x28,0x22,0x22,0x02,0x2a,0x2a}, // j {4,0x04,0x0a,0x06,0x26,0x26,0x48,0x26,0x44}, // k {2,0x2a,0x25,0x25,0x34}, // l {4,0x04,0x08,0x08,0x48,0x48,0x44,0x28,0x24}, // m {3,0x04,0x08,0x08,0x48,0x48,0x44}, // n {4,0x04,0x08,0x08,0x48,0x48,0x44,0x44,0x04}, // o {4,0x04,0x44,0x44,0x48,0x48,0x08,0x08,0x02}, // p {4,0x44,0x04,0x04,0x08,0x08,0x48,0x48,0x42}, // q {3,0x14,0x18,0x16,0x38,0x38,0x48}, // r {5,0x48,0x08,0x08,0x06,0x06,0x46,0x46,0x44,0x44,0x04}, // s {5,0x19,0x15,0x15,0x24,0x24,0x34,0x34,0x45,0x08,0x38}, // t {3,0x08,0x04,0x04,0x44,0x44,0x48}, // u {4,0x08,0x06,0x06,0x24,0x24,0x46,0x46,0x48}, // v {4,0x08,0x04,0x04,0x26,0x26,0x44,0x44,0x48}, // w {2,0x08,0x44,0x04,0x48}, // x {4,0x08,0x04,0x04,0x44,0x48,0x42,0x42,0x02}, // y {3,0x08,0x48,0x48,0x04,0x04,0x44}, // z {6,0x44,0x34,0x34,0x16,0x16,0x18,0x18,0x3a,0x3a,0x4a,0x07,0x17}, // { {2,0x2a,0x28,0x26,0x24}, // | {6,0x04,0x14,0x14,0x36,0x36,0x38,0x38,0x1a,0x1a,0x0a,0x37,0x47}, // } {3,0x09,0x1a,0x1a,0x38,0x38,0x49}, // ~ {3,0x05,0x45,0x07,0x47,0x09,0x49} // del }; // these get methods mirror those of java.awt.FontMetrics public static int getMaxAscent() { return MAXASCENT; } public static int getMaxDescent() { return MAXDESCENT; } public static int getMaxAdvance() { return MAXADVANCE; } public static int getLeading() { return LEADING; } public static int getHeight() { return MAXASCENT + MAXDESCENT + LEADING; } public static int stringWidth(String str) { int len = str.length(); if(len < 1) return 0; else return CHARWIDTH + ((len - 1) * MAXADVANCE); // gatepost effect } public static int getMaxAscent(double scale) { // used for horizontal text only return (int)(scale * MAXASCENT); } public static int getMaxDescent(double scale) { return (int)(scale * MAXDESCENT); } public static int getMaxAdvance(double scale) { return (int)(scale * MAXADVANCE); } public static int getLeading(double scale) { return (int)(scale * LEADING); } public static int getHeight(double scale) { return getMaxAscent(scale) + getMaxDescent(scale) + getLeading(scale); } public static int stringWidth(String str, double scale) { int len = str.length(); if (len < 1) return 0; else return (int)((scale * CHARWIDTH) + (scale * (len - 1) * MAXADVANCE)); } // 3 methods for drawing strings - fixed size, variable size, or variable size and rotation public static void drawString(Graphics g, String str, int x, int y) { int c, i, j, p, x1, y1, x2, y2, nv2, xoff, yoff; byte charvecs[]; int numch = str.length(); xoff = x; yoff = y + 1 + MAXDESCENT; for(i=0; i MAXCHAR)) c = 0; charvecs = gchars[c]; nv2 = 2 * charvecs[0]; for(j = 1; j < nv2; j += 2) { p = charvecs[j]; x1 = xoff + (p >>> 4); y1 = yoff - (p & 15); p = charvecs[j+1]; x2 = xoff + (p >>> 4); y2 = yoff - (p & 15); g.drawLine(x1, y1, x2, y2); } } } public static void drawString(Graphics g, String str, int x, int y, double scale) { int c, i, j, p, x1, y1, x2, y2, nv2, xoff, yoff, xinc; byte charvecs[]; int numch = str.length(); xoff = x; // yoff = (int)(scale * (1 + MAXDESCENT) + y + 0.5); // xinc = (int)(scale * MAXADVANCE + 0.5); yoff = y + (int)(scale * (1 + MAXDESCENT)); xinc = (int)(scale * MAXADVANCE); for(i=0; i MAXCHAR)) c = 0; charvecs = gchars[c]; nv2 = 2 * charvecs[0]; for(j = 1; j < nv2; j += 2) { p = charvecs[j]; x1 = xoff + (int)(scale * (p >>> 4)); y1 = yoff - (int)(scale * (p & 15)); p = charvecs[j+1]; x2 = xoff + (int)(scale * (p >>> 4)); y2 = yoff - (int)(scale * (p & 15)); g.drawLine(x1, y1, x2, y2); } } } public static void drawString(Graphics g, String str, int x, int y, double scale, double angle) { // angle is in DEGREES. // after much experimentation decided that it is worth properly // rounding the answers. // Notice that this routine can give a slightly longer length of text // when scale is non-integer and angle = 0.0 than the version above, // which makes each character the same width. int c, cx, cy, i, j, p, x1, y1, x2, y2, nv2, xoff, yoff, xinc; double scos, ssin; byte charvecs[]; int numch = str.length(); xoff = 0; yoff = 1 + MAXDESCENT; xinc = MAXADVANCE; angle = angle * Math.PI / 180.0; scos = scale * Math.cos(angle); ssin = scale * Math.sin(angle); for(i=0; i MAXCHAR)) c = 0; charvecs = gchars[c]; nv2 = 2 * charvecs[0]; for(j = 1; j < nv2; j += 2) { p = charvecs[j]; cx = xoff + (p >>> 4); cy = yoff - (p & 15); // this is exact but a tad slow: x1 = (int)(x + 0.5 + ((cx * scos) + (cy * ssin))); y1 = (int)(y + 0.5 + ((cy * scos) - (cx * ssin))); // whereas this may be faster but less accurate: // x1 = x + (int)((cx * scos) + (cy * ssin)); // y1 = y + (int)((cy * scos) - (cx * ssin)); p = charvecs[j+1]; cx = xoff + (p >>> 4); cy = yoff - (p & 15); x2 = (int)(x + 0.5 + ((cx * scos) + (cy * ssin))); y2 = (int)(y + 0.5 + ((cy * scos) - (cx * ssin))); // x2 = x + (int)((cx * scos) + (cy * ssin)); // y2 = y + (int)((cy * scos) - (cx * ssin)); g.drawLine(x1, y1, x2, y2); } } } }