import java.applet.Applet; import java.awt.image.*; import java.awt.event.*; import java.awt.*; import java.io.*; import java.net.URL; import java.util.*; /** * Four dimensional viewer. * @author David McQuillan * @version 0.1, 20 Feb 2001 */ public class Tessera extends Applet { /** * 4d Controls. */ private class HyperControls extends Panel implements ActionListener { HyperCanvas canvas; Button b_real, b_hyper, b_inout; Button b_spheres, b_reveal, b_slice; boolean spheres = true; boolean reveal = false; boolean slice = false; public HyperControls(HyperCanvas canvas) { Button b = null; this.canvas = canvas; b = new Button("Real"); b.addActionListener(this); add(b); b_real = b; b = new Button("Hyper"); b.addActionListener(this); add(b); b_hyper = b; b = new Button("InOut"); b.addActionListener(this); add(b); b_inout = b; add(new Label(" ")); b = new Button("Reveal"); b.addActionListener(this); add(b); b_reveal = b; b = new Button("Spheres"); b.addActionListener(this); add(b); b_spheres = b; b = new Button("Slice"); b.addActionListener(this); add(b); b_slice = b; b_real.setBackground(Color.yellow); b_spheres.setBackground(Color.yellow); b_slice.setVisible(false); } public void actionPerformed(ActionEvent ev) { String label = ev.getActionCommand(); Button b = null; if (label.equals("Real")) { b = b_real; canvas.set_move_real(); } else if (label.equals("Hyper")) { b = b_hyper; canvas.set_move_hyper(); } else if (label.equals("InOut")) { b = b_inout; canvas.set_move_inout(); } else if (label.equals("Reveal")) { reveal = ! reveal; canvas.set_reveal(reveal); b_reveal.setBackground( reveal ? Color.yellow : Color.lightGray); } else if (label.equals("Spheres")) { spheres = ! spheres; canvas.set_spheres(spheres); b_spheres.setBackground( spheres ? Color.yellow : Color.lightGray); } else if (label.equals("Slice")) { slice = ! slice; canvas.set_slice(slice); b_slice.setBackground( spheres ? Color.yellow : Color.lightGray); } if (b != null) { if (b != b_real) b_real.setBackground(Color.lightGray); if (b != b_hyper) b_hyper.setBackground(Color.lightGray); if (b != b_inout) b_inout.setBackground(Color.lightGray); b.setBackground(Color.yellow); } } } /* vertices at the ends of lines */ static final int[] v1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 3, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 9, 9, 9, 9, 10, 10, 10, 10, 12, 12, 12, 12, 15, 15, 15, 15}; static final int[] v2 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 4, 8, 1, 2, 7, 11, 4, 7, 1, 13, 7, 4, 2, 14, 8, 11, 13, 1, 11, 8, 14, 2, 13, 14, 8, 4, 14, 13, 11, 7}; /* Do the Archimedes dither colours for now!!! */ static final int[][] dimcol = {{0,7},{1,6},{2,5},{3,4}}; static final int[][] palette = { {255, 0, 0}, { 0,255, 0}, { 0, 0,255}, { 80, 80, 80}, {255,255,255}, {120,120,120}, {120,120,120}, {120,120,120} }; private class HyperModel { /* vertex info */ boolean[] bp; float[][] p; int[] ff, px, py; Color[] colour; Color light_gray = new Color(221,221,221); /* background circle */ Color line_gray = new Color(180,221,221); /* centre of lines */ Color line_dark = new Color( 80, 0, 0); /* outside of lines */ /* back to front paint sequence */ /* 0..15 are vertices, 16..47 are lines */ int[] seq; int[] pz; /* Points for a polygon */ int[] xp = new int[4]; int[] yp = new int[4]; HyperModel(InputStream is) throws Exception { /* Initialise painting control */ bp = new boolean[16]; p = new float[16][4]; ff = new int[16]; px = new int[16]; py = new int[16]; colour = new Color[48]; for (int i=0; i<=15; i++) { int r = 2; int g = 2; int b = 2; for (int j=0; j<=3; j++) { int k = dimcol[j][(i & (1< 0 ? tk0 : -tk0) * c.hd > 1) vism |= 1 << k; if (tk0 > 0) posm |= 1 << k; } for (int j = 0; j <= 15; j++) bp[j] = (((j ^ posm) & vism) != 0); } } /* Generate X Perspective */ for (int k = 0; k <= 15; k++) { int gs = (int)(c.scmult / (((c.hd-p[k][0]) * (c.rd-p[k][3])) / c.size)); ff[k] = (int) (gs / 4); px[k] = (int) (gs * p[k][1]); py[k] = (int) (gs * p[k][2]); pz[k] = (int) (gs * p[k][3]); } /* distance to centre of lines */ for (int i = 16; i <= 47; i++ ) pz[i] = (pz[v1[i]] + pz[v2[i]]) >> 1; /* * Order vertices and lines by distance * Not perfect 3D but seems to work okay mostly * Should be almost in order already */ { int sk = seq[0]; int pzk = pz[sk]; for (int k = 1; k <= 47; k++) { int sn; int pzn; if (pzk > (pzn = pz[sn = seq[k]])) { int n = k; do { seq[n--] = sk; } while (n >= 1 && pz[sk = seq[n-1]] > pzn); seq[n] = sn; sn = seq[k]; pzn = pz[sn]; } sk = sn; pzk = pzn; } } } public void paint(Graphics g, HyperCanvas c) { int width = c.getSize().width; int height = c.getSize().height; int size = (width < height ? width : height) / 2; int radius = 4*size/5; /* background circle */ g.setColor(light_gray); g.drawArc(width/2-radius,height/2-radius,2*radius,2*radius,0,360); /* Draw Vertex Spheres and lines */ for (int k = 0; k <= 47; k++) { int i = seq[k]; if (i <= 15 && bp[i]) { if (c.spheres) c.plot_circlefill(g, px[i], py[i], ff[i], colour[i]); } else if (i > 15 && bp[v1[i]] && bp[v2[i]]) { int a = v1[i]; int b = v2[i]; int xa = px[a]; int xb = px[b]; int ya = py[a]; int yb = py[b]; int za = pz[a]; int zb = pz[b]; int fa = ff[a]; int fb = ff[b]; int dx = xb - xa; int dy = yb - ya; int dz = zb - za; int dr2 = dx * dx + dy * dy + 1; /* avoid zero divide */ double dr = Math.sqrt(dr2); double dc = dx/dr; double ds = dy/dr; double exa = fa*dc/8; double eya = fa*ds/8; double exb = fb*dc/8; double eyb = fb*ds/8; double dxa = 0; double dya = 0; double dxb = 0; double dyb = 0; if (c.spheres) { double dsi = dr/Math.sqrt(dr2+dz*dz); dxa = fa*dc*dsi; dya = fa*ds*dsi; dxb = fb*dc*dsi; dyb = fb*ds*dsi; } g.setColor(colour[i]); xp[0] = (xa+width+(int)(dxa+eya))/2; yp[0] = (height-ya-(int)(dya-exa))/2; xp[1] = (xa+width+(int)(dxa-eya))/2; yp[1] = (height-ya-(int)(dya+exa))/2; xp[2] = (xb+width-(int)(dxb+eyb))/2; yp[2] = (height-yb+(int)(dyb-exb))/2; xp[3] = (xb+width-(int)(dxb-eyb))/2; yp[3] = (height-yb+(int)(dyb+exb))/2; g.fillPolygon(xp, yp, 4); g.setColor(line_gray); g.drawLine((xa+width+(int)dxa)/2,(height-ya-(int)dya)/2, (xb+width-(int)dxb)/2,(height-yb+(int)dyb)/2); } } } } class HyperCanvas extends Panel implements MouseListener, MouseMotionListener { HyperModel md = null; final int MOVE_REAL = 1; final int MOVE_INOUT = 2; final int MOVE_HYPER = 3; final float EPS = 0.1f; boolean reveal = false; boolean spheres = true; boolean slice = false; int move_type = MOVE_REAL; double[][] t; /* 4d rotation matrix */ int width, height, size, radius; double r1, t1, cr1, sr1, ct1, st1; double r2, t2, cr2, sr2, ct2, st2; double fact = 1.0; double hd = 4.0; double rd = 6.0; double scmax, scmult; Image backBuffer; Graphics backGC; Dimension backSize; HyperCanvas() { /* 4d rotation starts as identity */ t = new double[4][4]; for (int i=0; i <= 3; i++) t[i][i] = 1.0; /* Set up initial position */ for (int i = 0; i <= 5; i++) { final double c = Math.cos(0.1); final double s = Math.sin(0.1); for (int j = 1; j <= 3; j++) rotate(0, j, c, s); } scmax = fact * max_width (hd, rd); scmult = 2.0 / scmax; setBackground(Color.white); addMouseListener(this); addMouseMotionListener(this); } double max_width (double hd, double rd) { double r = 2.25; /* diagonal plus ball */ r = r / Math.sqrt(hd*hd - r*r); return r / Math.sqrt(rd*rd - r*r); } public void set_model(HyperModel m) { md = m; view(); } /* rotate 4d in axis plane a,b */ public void rotate(int a, int b, double c, double s) { for (int i = 0; i <= 3; i++) { double t1 = t[i][a]; double t2 = t[i][b]; t[i][a] = c*t1 + s*t2; t[i][b] = -s*t1 + c*t2; } } void view () { if (md != null) { md.calculate(this); repaint(); } } public void set_reveal (boolean value) { reveal = value; view(); } public void set_spheres (boolean value) { spheres = value; view(); } public void set_slice (boolean value) { slice = value; view(); } public void set_move_real () { move_type = MOVE_REAL; view(); } public void set_move_inout () { move_type = MOVE_INOUT; view(); } public void set_move_hyper () { move_type = MOVE_HYPER; view(); } void push_mouse_position () { t1 = t2; r1 = r2; ct1 = ct2; st1 = st2; cr1 = cr2; sr1 = sr2; } void set_mouse_position (int x, int y) { x = 2*x - getSize().width; y = getSize().height - 2*y; r2 = Math.PI * Math.sqrt(x*x+y*y) / (4 * radius); t2 = Math.atan2(y, x); cr2 = Math.cos(r2); sr2 = Math.sin(r2); ct2 = Math.cos(t2); st2 = Math.sin(t2); } public void mouse_move (int xpos, int ypos) { set_mouse_position(xpos, ypos); } public void mouse_drag (int xpos, int ypos) { push_mouse_position(); set_mouse_position(xpos, ypos); if (r1 != r2 && t1 != t2) { double r3, t3, cr3, sr3 ,ct3, st3; double x2, y2, z2; double x3, y3, z3; /* turn 1 on z axis till on xz plane */ x2 = sr2 * (ct2*ct1 + st2*st1); y2 = sr2 * (st2*ct1 - ct2*st1); z2 = cr2; /* turn 1 on y axis till z == 1 */ x3 = x2*cr1 - z2*sr1; y3 = y2; z3 = x2*sr1 + z2*cr1; /* turn 2 on z axis till y == 0 */ r3 = Math.sqrt(x3*x3+y3*y3); t3 = Math.atan2(y3, x3); /* turn till 1 goes to 2 on y axis */ r3 = Math.asin(r3); cr3 = Math.cos(r3); sr3 = Math.sin(r3); ct3 = Math.cos(t3); st3 = Math.sin(t3); if (move_type == MOVE_INOUT) { double nfact = fact * (1 + EPS*ct1*ct3*r3); double nhd = hd * (1 - EPS*st1*ct3*r3); double nrd = rd * (1 - EPS*r1*st3*r3); double lmax = nfact * (double)max_width((float)nhd, (float)nrd); if (lmax < 2*scmax && lmax > scmax/100) { fact = (float)nfact; hd = (float)nhd; rd = (float)nrd; scmult = 2*fact / scmax; } } else { /* position as described above */ rotate(2, 1, ct1, -st1); rotate(1, 3, cr1, -sr1); rotate(2, 1, ct3, -st3); /* if hyper then cross-product to x axis */ if (move_type == MOVE_HYPER) rotate(0, 1, cr3, -sr3); else rotate(1, 3, cr3, sr3); /* reverse manipulations */ rotate(2, 1, ct3, st3); rotate(1, 3, cr1, sr1); rotate(2, 1, ct1, st1); } } view(); } void plot_circlefill(Graphics g, int x, int y, int r, Color c) { g.setColor(c); g.fillArc((width+x-r)/2, (height-y-r)/2, r, r, 0, 360); } void plot_line(Graphics g, int xa, int ya, int xb, int yb) { g.setColor(Color.black); g.drawLine((width+xa)/2, (height-ya)/2, (width+xb)/2, (height-yb)/2); } void set_size(Dimension d) { width = d.width; height = d.height; size = (width < height ? width : height) / 2; radius = 4*size/5; view(); } public void paint(Graphics g) { if (backSize == null || !backSize.equals(getSize())) { backBuffer = createImage(getSize().width, getSize().height); backGC = backBuffer.getGraphics(); backSize = getSize(); set_size(backSize); } backGC.setColor(getBackground()); backGC.fillRect(0,0,backSize.width,backSize.height); md.paint(backGC, this); g.drawImage(backBuffer, 0, 0, this); } public void update(Graphics g) { paint(g); } public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { mouse_move(e.getX(), e.getY()); e.consume(); } public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseDragged(MouseEvent e) { mouse_drag(e.getX(), e.getY()); e.consume(); } public void mouseMoved(MouseEvent e) {} } HyperControls controls; HyperCanvas canvas; String mdname = null; HyperModel md; String message = null; public void init() { int width = getSize().width; int height = getSize().height; if (width <= 20) width = 400; if (height <= 20) height = 400; resize(width, height); setLayout(new BorderLayout()); canvas = new HyperCanvas(); controls = new HyperControls(canvas); add("South", controls); add("Center", canvas); mdname = getParameter("model"); if (mdname == null) mdname = "tesseract.4d"; try { InputStream is = null; // is = new URL(getDocumentBase (), mdname).openStream(); HyperModel m = new HyperModel(is); canvas.set_model(m); md = m; } catch(Exception e) { e.printStackTrace(); md = null; message = e.toString(); } } public void paint(Graphics g) { canvas.paint(g); } public void update(Graphics g) { paint(g); } public String getAppletInfo() { return "Title: tessera \nAuthor: David McQuillan - view a 4d object."; } public String[][] getParameterInfo() { String[][] info = { {"model", "path string", "The path to the model to be displayed in .4d format. Default is tesseract.4d"} }; return info; } }