Java Source Code for Stereoscopic Animated Hypercube

See also: My Critique of this Code.

// | Java Source Code for Stereoscopic Animated HyperCube applet
// | This is a Java 1.0 applet.
// | Copyright (c) Mark Newbold, 1996-2014
// | Last modified May 28, 2014
// |   (Changed name of "object" parameter to "obj" due to conflict with Java 1.3 Plug-in.)
// | Usable by permission for noncommercial purposes.
// | http://dogfeathers.com
// |
// | Optional Parameters:
// |   NAME=obj         VALUE=cube or VALUE=24cell or VALUE=crosspoly or VALUE=simplex
// |   NAME=projection  VALUE=n    where n is a number between 0 and 95, divisible by 5
// |   NAME=speed       VALUE=n    where n is a number between 1 and 100

import java.awt.*;
import java.lang.Math;
import java.util.*;

public class HyprCube extends java.applet.Applet
{
    private HyprCubeFrame myFrame;
    private Container myParent;
    private HyprCubePnl3 pnlDraw;
    private Panel pnlCtl;

    private Label  lblProj;
    private Label  lblSpeed;
    private Button btnProjMinus;
    private TextField txProj;
    private Button btnProjPlus;
    private Button btnSpeedMinus;
    private TextField txSpeed;
    private Button btnSpeedPlus;

    private Button btnStartStop;
    private Button btnDetach;
    private Button btnStereo;
    private Dimension dimOrigSize;

    boolean bStandalone = false;

    // Parameters
    private int objnum = 1;  // 1=cube 2=24-cell 3=cross-polytope 4=simplex
    private int proj = 0;    // current projection factor
    private int speed = 10;  // current speed
    private int maxspeed = 100;   // max allowed speed
    private int stereo_opt = 0;   // 0=red-blue  1=red-green  2=look-crossed

    double getProj() { return Math.sqrt(proj/100.0); }
    double getSpeed() { return Math.sqrt(speed / 10.0); }
    int getStereoOpt() { return stereo_opt; }
    int getObjnum() { return objnum; }

    private void putInFrame()
    {
        // hang onto the size for when we removeFromFrame
        dimOrigSize = new Dimension(size());

        myParent = getParent();

        String strFrameName;

        switch (objnum) {
            case 2:  strFrameName = "24-Cell"; break;
            case 3:  strFrameName = "Cross Polytope"; break;
            case 4:  strFrameName = "Simplex"; break;
            default: strFrameName = "Hypercube"; break;
        }

        myFrame = new HyprCubeFrame(this, strFrameName);
        Dimension siz = new Dimension(dimOrigSize);
        myFrame.resize(siz);
        myFrame.add("Center",this);
        myFrame.show();
        if (btnDetach != null) btnDetach.setLabel(" Attach ");
    }

    void removeFromFrame()
    {
        myFrame.remove(this);
        myFrame.dispose();
        myFrame = null;
        myParent.add("Center",this);
        resize(dimOrigSize);
        myParent.show();
        if (btnDetach != null) btnDetach.setLabel(" Detach ");
    }

    static public void main(String args[])
    {
        HyprCube hc = new HyprCube();

        int len = args.length;
        if (len >= 1) hc.parseObjParam(args[0]);
        if (len >= 2) hc.parseProjParam(args[1]);
        if (len >= 3) hc.parseSpeedParam(args[2]);
        hc.bStandalone = true;
        hc.resize(new Dimension(400,458));
        hc.init();
        hc.putInFrame();
        hc.start();
    }

    private void parseObjParam(String paramString)
    {
        String objNames[] = { "cube", "24cell", "crosspoly", "simplex" };

        int m;
        for (m=0; m < 4; m++)
            if (paramString.equalsIgnoreCase(objNames[m])) { objnum = m+1; return; }
    }

    private void parseProjParam(String paramString)
    {
        Integer newproj = new Integer(0);   // Integer wrapper
        int newp;
        newp = newproj.parseInt(paramString);
        if (newp < 0) newp = 0;
        if (newp > 95) newp = 95;
        proj = newp - (newp % 5);
    }

    private void parseSpeedParam(String paramString)
    {
        Integer newspeed = new Integer(0);   // Integer wrapper
        int newsp;
        newsp = newspeed.parseInt(paramString);
        if (newsp < 1) newsp = 1;
        if (newsp > maxspeed) newsp = maxspeed;
        speed = newsp;
    }

    public void init()
    {
        System.out.println(getAppletInfo());

        if (!bStandalone) {
            // Check parameters
            String paramString;

            paramString = getParameter("obj");
            if (paramString != null) parseObjParam(paramString);

            paramString = getParameter("projection");
            if (paramString != null) parseProjParam(paramString);

            paramString = getParameter("speed");
            if (paramString != null) parseSpeedParam(paramString);
        }

        setLayout(new BorderLayout());

        pnlDraw = new HyprCubePnl3(this);
        add("Center",pnlDraw);

        pnlCtl = new Panel();

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();

        pnlCtl.setLayout(gridbag);

        lblProj = new Label("Projection:");
        lblSpeed = new Label("Speed:");

        btnProjMinus = new Button(" - ");
        txProj = new TextField("XXXX");
        txProj.setEditable(false);
        btnProjPlus = new Button(" + ");

        btnSpeedMinus = new Button(" - ");
        txSpeed = new TextField("XXXX");
        txSpeed.setEditable(false);
        btnSpeedPlus = new Button(" + ");

        btnStartStop = new Button(" Stop ");
        btnStereo = new Button(" Stereo ");

        c.insets = new Insets(5,0,0,0);
        c.weightx = 0.1;
        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = 1;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints(lblProj, c);
        pnlCtl.add(lblProj);

        c.gridx = 4;
        c.gridy = 0;
        c.gridwidth = 1;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints(lblSpeed, c);
        pnlCtl.add(lblSpeed);

        if (!bStandalone) {
            btnDetach = new Button(" Detach ");
            c.gridx = 7;
            c.weightx = 4.0;
            c.anchor = GridBagConstraints.EAST;
            gridbag.setConstraints(btnDetach, c);
            pnlCtl.add(btnDetach);
        }

        c.insets = new Insets(0,0,5,0);
        c.gridy = 1;
        c.gridx = GridBagConstraints.RELATIVE;
        c.gridwidth = 1;
        c.weightx = 0.4;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints(btnProjMinus, c);
        pnlCtl.add(btnProjMinus);

        c.weightx = 1.0;
        c.anchor = GridBagConstraints.CENTER;
        c.fill = GridBagConstraints.HORIZONTAL;
        gridbag.setConstraints(txProj, c);
        pnlCtl.add(txProj);

        c.weightx = 3.0;
        c.anchor = GridBagConstraints.WEST;
        c.fill = GridBagConstraints.NONE;
        gridbag.setConstraints(btnProjPlus, c);
        pnlCtl.add(btnProjPlus);

        c.gridx = GridBagConstraints.RELATIVE;
        c.gridwidth = 1;
        c.weightx = 0.4;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints(btnSpeedMinus, c);
        pnlCtl.add(btnSpeedMinus);

        c.weightx = 1.0;
        c.anchor = GridBagConstraints.CENTER;
        c.fill = GridBagConstraints.HORIZONTAL;
        gridbag.setConstraints(txSpeed, c);
        pnlCtl.add(txSpeed);

        c.weightx = 3.0;
        c.anchor = GridBagConstraints.WEST;
        c.fill = GridBagConstraints.NONE;
        gridbag.setConstraints(btnSpeedPlus, c);
        pnlCtl.add(btnSpeedPlus);

        c.gridwidth = 1;
        c.weightx = 4.0;
        c.gridx = 6;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints(btnStartStop, c);
        pnlCtl.add(btnStartStop);

        c.gridx = 7;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints(btnStereo, c);
        pnlCtl.add(btnStereo);

        add("South",pnlCtl);

        setTxProj();
        setTxSpeed();

        pnlDraw.init();
        pnlDraw.show();
        start();
    }

    public void start()
    {
        btnStartStop.setLabel(" Stop ");
        pnlDraw.start();
    }

    public void stop()
    {
        btnStartStop.setLabel(" Start ");
        pnlDraw.stop();
    }

    public boolean action(Event evt, Object arg)
    {
        if (evt.id == evt.ACTION_EVENT) {
            int newproj = proj;

            if ((evt.target == btnProjMinus) && (newproj >= 5)) newproj -= 5;
            if ((evt.target == btnProjPlus) && (newproj <= 90)) newproj += 5;
            if (newproj != proj) {
                proj = newproj;
                setTxProj();
                pnlDraw.repaint();
                return true;
            }

            int newspeed = speed;
            if ((evt.target == btnSpeedMinus) && (newspeed > 1)) newspeed--;
            if ((evt.target == btnSpeedPlus) && (newspeed < maxspeed)) newspeed++;
            if (newspeed != speed) {
                speed = newspeed;
                setTxSpeed();
                return true;
            }

            if (evt.target == btnStartStop) {
                if (arg == " Stop ") stop();
                else
                if (arg == " Start ") start();
                return true;
            }

            if ((evt.target == btnDetach) && (btnDetach != null)) {
                if (myFrame == null) putInFrame();
                else removeFromFrame();
                return true;
            }

            if (evt.target == btnStereo) {
                stereo_opt++;
                if (stereo_opt > 2) stereo_opt = 0;
                pnlDraw.makecolors();
                pnlDraw.repaint();
            }
        }

        return false;
    }

    private void setTxProj()
    {
        Double dbl = new Double(proj/100.0);  // Double wrapper
        txProj.setText(dbl.toString());
    }

    private void setTxSpeed()
    {
        Integer spd = new Integer(speed);  // Integer wrapper
        txSpeed.setText(spd.toString());
    }

    public String getAppletInfo()
    {
        return "HyprCube applet v1.3 Copyright 1996-2014 Mark Newbold\nLast updated: May 28, 2014\nAuthor: Mark Newbold\nhttp://dogfeathers.com";
    }
}


class HyprCubePnl3 extends Panel implements Runnable
{
    private Image offscreenImg;
    private Graphics offscreenG;
    private Dimension offscreensize;
    private Thread runner;
    private Random rand = new Random();

    private final double velmax = .03;  // max velocity, radians per cycle
    private final double velinc = .006; // velocity increment, radians
    private final int    delay  = 50;   // sleep milliseconds

    private double vertices[][];  // vertex coords in 4-space
    private int vert2xR[];   // screen coords of vertices
    private int vert2xL[];
    private int vert2y[];

    private byte edges[][];       // "from" and "to" vertex indices
    private double vel[][] = new double[4][4];
    private double m1[][] = new double[4][4];
    private double m2[][] = new double[4][4];
    private double rot4[][] = new double[4][4];
    private double ROT4[][] = new double[4][4];
    private double ROT4A[][] = new double[4][4];
    private double newROT[][] = new double[4][4];
    private double ROTM[][] = new double[4][4];
    private double holdROT[][];
    private double rotvert[] = new double[4];
    private double vec1[] = new double[3];
    private double vec2[] = new double[3];
    private double vec3[] = new double[3];

    private double R4;     // radius in 4-space
    private int dx,dy;     // screen width,height
    private int dx_offset; // dx shift for stereo opt 2 (look-crossed)
    private int xbase,ybase;  // center of screen, in pixels

    // projection parameters
    // Calculated by "calcProjParms"
    private double fac,dfac,deps,deltar,vpfR,R3,epsfac;

    private boolean bLeftFirst;
    private boolean bTracking = false;
    private boolean bShiftDown;
    private int mouseX, mouseY;  // last mouse X and Y

    private Color leftColor,rightColor,backgColor;

    private HyprCube owner;

    HyprCubePnl3(HyprCube own)  // constructor
    {
        owner = own;
    }

    private void defineCube()
    {
        int i,j,k,dif,ct;

        vertices = new double[16][4];
        edges = new byte[32][2];

        // create the vertices
        for (i=0; i < 16; i++) {
            for (j=0; j < 4; j++) vertices[i][j] = ((i >> (3-j)) & 1) - 0.5;
        }

        // Create the edges
        // Considering each vertex to be a 4-bit bit-pattern, there
        //   is an edge between each pair of vertices that differ in only
        //   one bit.
        k = 0;
        for (i=0; i < 15; i++) {
            for (j=i+1; j < 16; j++) {
                ct = 0;
                for (dif=i^j; dif != 0; dif >>= 1) if ((dif&1) != 0) ct++;
                if (ct == 1) {
                    edges[k][0] = (byte)i;
                    edges[k][1] = (byte)j;
                    k++;
                }
            }
        }
    }

    private void define24Cell()
    {
        byte bitss[] = new byte[24];  // 4-bit values labelling the
                                      //    hypercube squares
        byte masks[] = new byte[24];  // masks indicating which
                                      //    particular 2 bits are significant
        int mask,bits;
        int i,j,k,m,n,d,e;

        vertices = new double[24][4];  // vertex coords in 4-space
        edges = new byte[96][2];    // "from" and "to" vertex indices

        // We construct the 24-Cell by making 3-d octahedra in each of the 3-cubes
        //   that form a hypercube.  The 6 vertices of the each octahedron touch
        //   in the center of each face of the 3-cube.
        // Hypercube vertices are labeled by 4-bit values.
        // A 3-cube within a hypercube is formed from all vertices
        //    that have the same value of a particular bit.
        // A 2-square is formed from all vertices that have the
        //    same values of 2 particular distinct bits.
        // We use the bitss and maskss arrays to label the vertices according to
        //   which 2-square they lie on.
        i = 0;   // vertex index
        for (m=0; m < 4; m++) {
            for (n=0; n < m; n++) {
                // m and n are a pair of distinct bit indexes
                mask = (1 << m) | (1 << n);
                for (j=0; j < 2; j++) {
                    for (k=0; k < 2; k++) {
                        bits = (j << m) | (k << n);
                        masks[i] = (byte)mask;
                        bitss[i] = (byte)bits;

                        for (d=0; d < 4; d++) {
                            vertices[i][d]
                              = ((mask >> d) & 1) != 0
                              ? 2 * ((bits >> d) & 1) - 1
                              : 0;
                        }
                        i++;
                    }
                }
            }
        }


        // Construct the 96
        // Loop thru the 3-cubes
        // A 3-cube is all vertices that have a single particular bit value in common.
        // We make an edge from the center of each 2-square of the 3-cube to
        //   the center of each adjacent 2-square.
        e = 0;
        for (m=0; m < 4; m++) {  // for each particular bit
            mask = (1 << m);
            for (n=0; n < 2; n++) {  // for each value of that bit
                bits = (n << m);

                // Loop thru all pairs of adjacent squares of the 3-cube.
                for (j=0; j < 24; j++) {
                    if ((mask & masks[j]) == 0)
                        continue;  // square doesn't belong to cube

                    if ((bits & mask) != (bitss[j] & mask))
                        continue;  // square doesn't belong to cube

                    for (k=0; k < j; k++) {
                        if ((mask & masks[k]) == 0)
                            continue;  // square doesn't belong to cube

                        if ((bits & mask) != (bitss[k] & mask))
                            continue;  // square doesn't belong to cube

                        if (masks[j] == masks[k])
                            continue;  // skip opposing squares

                        edges[e][0] = (byte)j;
                        edges[e][1] = (byte)k;
                        e++;
                    }
                }
            }
        }
    }

    private void defineCrossPoly()
    {
        vertices = new double[8][4];  // vertex coords in 4-space

        byte edges[][] = {    // "from" and "to" vertex indices
          { 0, 2 },
          { 0, 3 },
          { 1, 3 },
          { 1, 2 },
          { 0, 4 },
          { 1, 4 },
          { 2, 4 },
          { 3, 4 },
          { 0, 5 },
          { 1, 5 },
          { 2, 5 },
          { 3, 5 },
          { 0, 6 },
          { 1, 6 },
          { 2, 6 },
          { 3, 6 },
          { 4, 6 },
          { 5, 6 },
          { 0, 7 },
          { 1, 7 },
          { 2, 7 },
          { 3, 7 },
          { 4, 7 },
          { 5, 7 }
        };

        int i,j,k;

        // Create the vertices
        j = k = 0;
        for (i=0; i < 4; i++) {
            vertices[j++][k] = -1;
            vertices[j++][k] = 1;
            k++;
        }

        this.edges = edges;
    }

    private void defineSimplex()
    {
        vertices = new double[5][4];   // vertex coords in 4-space

        byte edges[][] = {    // "from" and "to" vertex indices
            { 0, 1 },
            { 0, 2 },
            { 0, 3 },
            { 0, 4 },
            { 1, 2 },
            { 1, 3 },
            { 1, 4 },
            { 2, 3 },
            { 2, 4 },
            { 3, 4 },
        };

        int i,j,k;

        // Create the vertices
        // vertices[0][?] is initialized to zeros (default initialization)
        // This represents a single point at the origin
        double dist,sumsq,avg;

        // Add additional dimensions 1, 2, 3 and 4
        for (i=1; i < 5; i++) {
            sumsq = 0.0;
            for (j=0; j < i; j++) {
                avg = 0.0;
                for (k=0; k < i; k++) avg += vertices[k][j];
                avg /= i;
                dist = (vertices[0][j] - avg);
                sumsq += (dist * dist);
                vertices[i][j] = avg;
            }
            vertices[i][i-1] = Math.sqrt(1.0 - sumsq);
        }

        centerTheObject();

        this.edges = edges;
    }

    private void centerTheObject()
    {
        int i,j;
        int len = vertices.length;
        double avg;
        for (i=0; i < 4; i++) {
            avg = 0.0;
            for (j=0; j < len; j++) avg += vertices[j][i];
            avg /= len;
            for (j=0; j < len; j++) vertices[j][i] -= avg;
        }
    }

    void makecolors()
    {
        int redval;
        int blueval;
        int backgval;

        switch (owner.getStereoOpt()) {
            case 0: // red-blue
                redval   = 188;
                blueval  = 255;
                backgval = 84;
                leftColor   = new Color(redval,backgval,0);
                rightColor  = new Color(0,backgval,blueval);
                backgColor = new Color(0,backgval,0);       // To minimize "ghosts" where one eye sees the other eye's image
                break;
            case 1: // red-green
                // Use Stefan Scheller's recommended colors:
                leftColor  = new Color(0,239,0);
                rightColor   = new Color(255,0,0);
                backgColor = new Color(222,222,222);
                break;
            case 2: // look-crossed
                backgval = 198;
                leftColor   = new Color(0,0,0);
                rightColor  = new Color(0,0,0);
                backgColor = new Color(backgval,backgval,backgval);
                break;
        }
    }

    public void init()
    {

        makecolors();

        // Seed the random number generator.
        Date date = new Date();
        rand.setSeed(date.getTime());

        switch (owner.getObjnum()) {
            case 1:
                defineCube();
                break;
            case 2:
                define24Cell();
                break;
            case 3:
                defineCrossPoly();
                break;
            case 4:
                defineSimplex();
                break;
            default:
                defineCube();
                break;
        }

        // Calculate the radius of the figure in 4-space.
        // Since all the vertices have the same radius, we just look at the first vertex.
        double sum = 0.0;
        int k;
        for (k=0; k < 4; k++) sum += vertices[0][k] * vertices[0][k];
        R4 = Math.sqrt(sum);

        // Alloc arrays for screen coords of vertices.
        k = vertices.length;
        vert2xR = new int[k];
        vert2xL = new int[k];
        vert2y  = new int[k];

        for (k=0; k < 4; k++) ROT4[k][k] = 1.0;
        for (k=0; k < 4; k++) ROTM[k][k] = 1.0;
    }

    public void run()
    {
        setBackground(backgColor);
        while (true) {
            rotate();
            repaint();
            try { Thread.sleep((int)(delay / owner.getSpeed())); }
            catch (InterruptedException e) { }
        }
    }

    public void update(Graphics g)
    {
        paint(g);
    }


    public void start() {
        if (runner == null) {
            runner = new Thread(this);
            runner.start();
        }
    }

    public void stop() {
        if (runner != null) {
            runner.stop();
            runner = null;
        }
    }

    public boolean mouseDown(Event evt, int x, int y)
    {
        if (!bTracking) {
            owner.stop();
            bShiftDown = evt.shiftDown();
            bTracking = true;
            mouseX = x;
            mouseY = y;
            repaint();
        }

        return true;
    }

    private double normalize3Vec(double vec[])
    {
        int m;
        double len,sq;

        len = 0.0;
        for (m=0; m < 3; m++) {
            sq = vec[m] * vec[m];
            len += sq;
        }
        len = Math.sqrt(len);
        for (m=0; m < 3; m++) vec[m] /= len;
        return len;
    }

    public boolean mouseDrag(Event evt, int x, int y)
    {
        if ((mouseX != x) || (mouseY != y)) {

            double s,c;
            int i,j,k;

            // Vector from center to previous mouse position
            vec1[0] = mouseX - xbase;
            vec1[1] = mouseY - ybase;
            vec1[2] = -epsfac;

            // Vector from center to current mouse position
            vec2[0] = x - xbase;
            vec2[1] = y - ybase;
            vec2[2] = -epsfac;

            normalize3Vec(vec1);
            normalize3Vec(vec2);

            // Get the dot product (the cosine of the angle)
            c = 0.0;
            for (k=0; k < 3; k++) c += vec1[k] * vec2[k];

            // The cross product:
            vec3[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
            vec3[1] = vec1[2] * vec2[0] - vec1[0] * vec2[2];
            vec3[2] = vec1[0] * vec2[1] - vec1[1] * vec2[0];

            s = normalize3Vec(vec3);   // Returns the sine of the angle

            // Make vec2 perpendicular to vec1 by subtracting off
            //    the part which is parallel.
            for (k=0; k < 3; k++) vec2[k] -= c * vec1[k];
            normalize3Vec(vec2);

            // Now vec1, vec2 and vec3 are an orthonormal basis

            // Build the 3-rotation matrix
            for (i=0; i < 3; i++) {
                for (j=0; j < 3; j++) {
                    rot4[i][j] = vec3[i] * vec3[j]
                               + c * (vec1[i] * vec1[j] + vec2[i] * vec2[j])
                               + s * (vec2[i] * vec1[j] - vec1[i] * vec2[j]);
                }
            }
            for (k=0; k < 3; k++) rot4[3][k] = rot4[k][3] = 0.0;
            rot4[3][3] = 1.0;

            // If shift key was held down, swap the w and z axes in the rotation matrix.
            if (bShiftDown) {
                for (k=0; k < 4; k++) {   // swap rows 2 and 3
                    c = rot4[2][k];
                    rot4[2][k] = rot4[3][k];
                    rot4[3][k] = c;
                }
                for (k=0; k < 4; k++) {   // swap columns 2 and 3
                    c = rot4[k][2];
                    rot4[k][2] = rot4[k][3];
                    rot4[k][3] = c;
                }
            }

            // Apply the small 3-rotation rot4 to the cumulative manual rotation ROTM:
            for (i=0; i < 4; i++) {
                for (j=0; j < 4; j++) {
                  newROT[i][j] = 0;
                  for (k=0; k < 4; k++) newROT[i][j] += rot4[i][k] * ROTM[k][j];
                }
            }

            // swap newROT with ROTM
            holdROT = ROTM;
            ROTM = newROT;
            newROT = holdROT;
        }

        mouseX = x;
        mouseY = y;
        repaint();

        return true;
    }

    public boolean mouseUp(Event evt, int x, int y)
    {
        if (bTracking) {
            bTracking = false;
            repaint();
        }
        return true;
    }

    // rotate
    // increment velocity vector and rotate the object
    private void rotate()
    {
        double angl,sinangl,cosangl,d,dsq,max,veli,vmax;
        int i,j,k,abi,abj;

        max = 0.0;
        abi = 1;
        abj = 2;

        veli = velinc * owner.getSpeed();
        vmax = velmax * owner.getSpeed();

        // The velocity matrix represents the rotation that is to be performed every cycle.
        // It is a 4x4 antisymmetric matrix, with a determinant of zero.
        // We now change it by a small antisymmetric amount (which generally makes the determinant non-zero).
        for (i=0; i < 3; i++) {
            for (j=i+1; j < 4; j++) {
                d = vel[i][j] + veli * (rand.nextDouble() - 0.5);
                vel[i][j] = d;
                vel[j][i] = -d;
                dsq = d * d;
                if (dsq > max) {      // hang onto the indices of the biggest element
                    max = dsq;
                    abi = i;
                    abj = j;
                }
            }
        }

        if (max < 1.0E-10) return;   // no rotation

        // calculate the square root of the determinant
        d =  vel[0][3] * vel[1][2]
            -vel[0][2] * vel[1][3]
            +vel[0][1] * vel[2][3];

        // We need to adjust so that the determinant is zero
        // (abi,abj) are the indices of the largest element
        // Determine the indices of that element's cofactor:
        switch (abi*10+abj) {
            case 1:  i=2; j=3; break;
            case 2:  i=3; j=1; break;
            case 3:  i=1; j=2; break;
            case 12: i=0; j=3; break;
            case 13: i=2; j=0; break;
            case 23: i=0; j=1; break;
            default: i=0; j=1; break;
        }

        // Adjust the cofactor to make the determinant zero.
        vel[i][j] -= d / vel[abi][abj];
        vel[j][i] = -vel[i][j];

        // Calculate the rotation angle (the sum of the squares of the vel elements)
        angl = 0;
        for (i=0; i < 3; i++) {
            for (j=i+1; j < 4; j++) angl += vel[i][j] * vel[i][j];
        }
        angl = Math.sqrt(angl);
        if (angl < 1.0E-5) return;    // no rotation

        // If the angle is too great, reduce all components of the velocity.
        // (Don't want it to rotate too fast.)
        if (angl > vmax) {
            d = vmax / angl;
            angl = vmax;
            for (i=0; i < 3; i++) {
                for (j=i+1; j < 4; j++) {
                    vel[i][j] *= d;
                    vel[j][i] = -vel[i][j];
                }
            }
        }

        // Now we need to build a rotation matrix from "vel".
        // The rotation matrix can be expressed symbolically as
        // R = lim          (I + (vel/n))^n
        //     (n->infinity)
        //
        // Where I is the identity matrix and  "^n" represents the operation
        //   of multiplying the matrix by itself n times.
        // We expand the exponential as a power series in the matrix "vel", noting that R = exp(vel)
        //   and using the standard power series expansion of the exponential.
        // The "vel" matrix has the property that vel . vel . vel = -angl * angl * vel
        //  (where "." is matrix multiplication)
        // Define a matrix m1 as vel / angl.
        // Then m1 . m1 . m1 = -m1
        // Odd powers of m1 can be written as m1^(2n+1) = (-1)^n * m1
        // Even powers of m1 can be written as m1^(2n+2) = (-1)^n * (m1 . m1)
        // Define m2 as m1 . m1
        // Odd powers of vel can be rewritten:  vel^(2n+1) = angl^(2n+1) * (-1)^n * m1
        // Even powers > 0 of vel can be rewritten: vel^(2n+2) = angl^(2n+2) * (-1)^n * m2
        // Rewrite the power series using m1 and m2.
        // The odd terms are a series expansion of sin(angl) * m1
        // The even terms with n > 0 are a series expansion of (1 - cos(angl)) * m2
        // The zero-order term is the identity matrix.

        // Build m1 by scaling vel by an appropriate factor.
        // m1 has the property that m1 . m1 . m1 = -m1
        // (where the "." is matrix multiplication)
        for (i=0; i < 4; i++) m1[i][i] = 0;
        for (i=0; i < 3; i++) {
            for (j=i+1; j < 4; j++) {
                m1[i][j] = vel[i][j] / angl;
                m1[j][i] = -m1[i][j];
            }
        }

        // Build m2, the square of m1:
        for (i=0; i < 4; i++) {
            for (j=i; j < 4; j++) {
                m2[i][j] = 0.0;
                for (k=0; k < 4; k++) m2[i][j] += (m1[i][k] * m1[k][j]);
                m2[j][i] = m2[i][j];
            }
        }

        // Build the rotation matrix
        cosangl = 1.0 - Math.cos(angl);
        sinangl = Math.sin(angl);
        for (i=0; i < 4; i++) {
            for (j=0; j < 4; j++) rot4[i][j] = sinangl*m1[i][j] + cosangl*m2[i][j];
        }
        for (i=0; i < 4; i++) rot4[i][i] += 1.0;

        // Apply the small rotation "rot4" to the cumulative rotation "ROT4"
        for (i=0; i < 4; i++) {
            for (j=0; j < 4; j++) {
                newROT[i][j] = 0.0;
                for (k=0; k < 4; k++) newROT[i][j] += rot4[i][k] * ROT4[k][j];
            }
        }

        // swap newROT with ROT4
        holdROT = ROT4;
        ROT4 = newROT;
        newROT = holdROT;
    }


    // calcProjParms
    // Calculate the following parameters which control the
    //    projection from 4D onto the 2D screen:
    //      fac
    //      dfac
    //      deps
    //      deltar
    //      vpfR
    //      R3
    //      epsfac

    private void calcProjParms()
    {
        // 3-D parameters:
        // These parameters are in units of r, the radius of the 3-sphere
        //   that encloses the 3-space projection of the 4-space object.
        double d = 8.0;       // distance from eyes to screen
        double eps = 1.5;     // distance from screen to center of 3-sphere
        double clip = 0.95;   // fraction of the panel to use

        double q,sx,sy,vpf;

        double delta = (owner.getStereoOpt() == 2) ? 0.5 : 0.3;  // distance from eye to nose

        vpf = owner.getProj(); // viewpoint factor  (0 <= vpf < 1)
                               // inverse of viewpoint 4-distance in units of R

        R3 = R4 / Math.sqrt(1 - vpf * vpf);      // radius in 3-space

        deps = d - eps;

        // Calculate projected size of the 2D image (sx,sy) for R3 == 1.
        q = Math.sqrt(deps*deps + delta*delta);
        sx = 2.0 * (d * Math.tan(Math.asin(1/q)+Math.atan(delta/deps)) - delta);
        if (owner.getStereoOpt() == 2) sx *= 2;

        q = Math.sqrt(deps*deps + delta*delta);
        sy = 2.0 * d * Math.tan(Math.asin(1/deps));

        if (dx * sy < dy * sx) fac = dx/sx;   // window is too tall, constrained by dx
        else                   fac = dy/sy;   // constrained by dy

        fac *= clip;
        epsfac = eps * fac;

        dx_offset = 0;
        if (owner.getStereoOpt() == 2) dx_offset = -(int)(fac * sx / 4);

        fac /= R3;

        deltar = delta * R3;
        vpfR = vpf / R4;
        dfac = d * fac;
    }


    public void paint(Graphics g)
    {
        Dimension dim = size();

        dx = dim.width;
        dy = dim.height;

        if ((dx < 1) || (dy < 1)) return;

        xbase = dx / 2;
        ybase = dy / 2;

        // For double buffering:
        if ((offscreenImg == null) || (dx != offscreensize.width) || (dy != offscreensize.height)) {
            offscreenImg = createImage(dx, dy);
            offscreensize = new Dimension(dim);
            offscreenG = offscreenImg.getGraphics();
            offscreenG.setFont(getFont());
        }

        // Draw background
        offscreenG.setColor(backgColor);
        offscreenG.fillRect(0,0,dx,dy);

        // Calculate projection parameters
        calcProjParms();

        int i,j,k,v,v1;
        double q,fac2,fac3,delt;

        if (owner.getStereoOpt() == 2) delt = -dx_offset;
        else                           delt = (fac-dfac/deps)*deltar;

        if (owner.getStereoOpt() != 2) {  // Draw a little square to focus on:
            offscreenG.setColor(rightColor);
            offscreenG.drawRect(xbase-2 + (int)delt, ybase-2, 4, 4);
            offscreenG.setColor(leftColor);
            offscreenG.drawRect(xbase-2 - (int)delt, ybase-2, 4, 4);
        }

        // Draw vector from center of object to mouse position
        if (bTracking) {
            offscreenG.setColor(rightColor);
            offscreenG.drawLine(xbase+(int)delt, ybase, mouseX, mouseY);
            offscreenG.setColor(leftColor);
            offscreenG.drawLine(xbase-(int)delt, ybase, mouseX, mouseY);
        }

        // Combine the manual rotation ROTM with the randomly-generated 4-rotation ROT4
        for (i=0; i < 4; i++) {
            for (j=0; j < 4; j++) {
                ROT4A[i][j] = 0.0;
                for (k=0; k < 4; k++) ROT4A[i][j] += ROTM[i][k] * ROT4[k][j];
            }
        }

        // Build 2d coords of all vertices
        int len = vertices.length;
        for (v=0; v < len; v++) {
            // Rotate the vertex
            for (j=0; j < 4; j++) {
                rotvert[j] = 0.0;
                for (k=0; k < 4; k++) rotvert[j] += (ROT4A[j][k] * vertices[v][k]);
            }
            fac2 = 1.0 / (1.0 - vpfR * rotvert[3]);
            for (k=0; k < 3; k++) rotvert[k] *= fac2;
            fac3 = dfac / (deps-rotvert[2]/R3);
            vert2y[v] = ybase + (int)(fac3*rotvert[1]);
            q = fac3*rotvert[0];
            delt = (fac-fac3)*deltar + dx_offset;
            vert2xR[v] = xbase + (int)(q+delt);
            vert2xL[v] = xbase + (int)(q-delt);
        }


        // Draw all the edges
        len = edges.length;
        for (i=0; i < len; i++) {
            v  = edges[i][0];   // vertex indices
            v1 = edges[i][1];
            if (bLeftFirst) {
                offscreenG.setColor(leftColor);
                offscreenG.drawLine(vert2xL[v],vert2y[v],vert2xL[v1],vert2y[v1]);
            }
            offscreenG.setColor(rightColor);
            offscreenG.drawLine(vert2xR[v],vert2y[v],vert2xR[v1],vert2y[v1]);
            if (!bLeftFirst) {
                offscreenG.setColor(leftColor);
                offscreenG.drawLine(vert2xL[v],vert2y[v],vert2xL[v1],vert2y[v1]);
            }
            bLeftFirst = !bLeftFirst;
        }

        g.drawImage(offscreenImg,0,0,this);
    }
}

class HyprCubeFrame extends Frame
{
    private HyprCube ghc;

    public HyprCubeFrame(HyprCube hc, String strName)
    {
        super(strName);
        ghc = hc;
    }


    public boolean handleEvent(Event evt)
    {
        if (evt.id == evt.WINDOW_DESTROY) {
            if (ghc.bStandalone) System.exit(0);
            else ghc.removeFromFrame();
            return true;
        }
        return false;
    }
}

<The Source Code as a .java file>

<Dogfeathers Home Page>   <Mark's Home Page>   <Mark's Java Stuff>
Email: Mark Newbold
This page URL: http://dogfeathers.com/java/hyprjava.html