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