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

AltStyle によって変換されたページ (->オリジナル) /