/* $Id: Space.java,v 1.6 2008-07-28 19:26:11 harmen Exp $ Play a stereoscopic 4d game around the hypercube (a.k.a. tesseract). See http://www.harmwal.nl/hypercube/ Copyright (C) 1998, 2006 Harmen van der Wal - http://www.harmwal.nl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package nl.harmwal.hypercube; import java.applet.*; import java.util.*; import java.awt.*; import java.awt.event.*; interface Spatial { void sum(int dimension, double value); void sum(Point point); void sum(Point point, double value); void multiply(double value); void mirror(int dimension); void transfigure(int dimension, double value); void rotate(int dimension1, int dimension2, double angle); void relative(Orientation orientation); void project(); Spatial getRelative(Orientation orientation); Spatial getProjected(Orientation orientation); void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics); void setColor(int colorType); } class Point implements Spatial { double[] coordinates; int colorType = ColorScheme.NORMAL; Point(int dimensions) { coordinates = new double[dimensions]; } Point(double[] coordinates) { this.coordinates = (double[])coordinates.clone(); } int getDimensions() { return coordinates.length; } double getCoordinate(int d) { return coordinates[d]; } double[] copyCoordinates() { return (double[])coordinates.clone(); } Point copyPoint() { return new Point((double[])coordinates.clone()); } double distance() { // to origin double distance = 0; for (int d = 0; d < coordinates.length; d++) { distance += coordinates[d] * coordinates[d]; } distance = Math.sqrt(distance); return distance; } double distance(Point point) { double[] coordinates = point.copyCoordinates(); double distance = 0; for (int d = 0; d < coordinates.length; d++) { distance += Math.pow(this.coordinates[d] - coordinates[d], 2); } distance = Math.sqrt(distance); return distance; } boolean inside(double value) { boolean inside = true; for (int d = 0; d < coordinates.length; d++) { if (Math.abs(coordinates[d]) > value) { inside = false; } } return inside; } double angle(int dimension1, int dimension2) { double angle = 0; if (coordinates[dimension1] == 0) { if (coordinates[dimension2] > 0) { angle = Math.PI / 2; }else{ angle = -Math.PI / 2; } }else { angle = Math.atan(coordinates[dimension2]/coordinates[dimension1]); } if (Math.abs(Math.cos(angle) - coordinates[dimension1]) > Math.abs(Math.cos(angle + Math.PI) - coordinates[dimension1])) { angle += Math.PI; } if (angle > Math.PI) { angle = angle - 2 * Math.PI; } return angle; } public void sum(int dimension, double value) { coordinates[dimension] += value; } public void sum(Point point) { double[] coordinates = point.copyCoordinates(); for (int d = 0; d < coordinates.length; d++) { this.coordinates[d] += coordinates[d]; } } public void sum(Point point, double value) { double[] coordinates = point.copyCoordinates(); double distance = point.distance(); for (int d = 0; d < coordinates.length; d++) { this.coordinates[d] += coordinates[d] * value / distance; } } Point difference(Point point) { double[] coordinates = new double[this.coordinates.length]; for (int d = 0; d < coordinates.length; d++) { coordinates[d] = this.coordinates[d] - point.getCoordinate(d); } return new Point(coordinates); } public void multiply(double value) { for (int d = 0; d < coordinates.length; d++) { coordinates[d] *= value; } } public void mirror(int dimension) { coordinates[dimension] *= -1; } public void rotate(int dimension1, int dimension2, double angle) { double tan = Math.sqrt(Math.pow(coordinates[dimension1], 2) + Math.pow(coordinates[dimension2], 2)); angle += angle(dimension1, dimension2); coordinates[dimension1] = tan * Math.cos(angle); coordinates[dimension2] = tan * Math.sin(angle); } public void relative(Orientation orientation) { Point point = orientation.relative(this); coordinates = point.copyCoordinates(); } public void transfigure(int dimension) { double[] coordinates = new double[this.coordinates.length - 1]; int d2 = 0; for (int d = 0; d < this.coordinates.length; d++) { if (d != dimension) { coordinates[d2++] = this.coordinates[d]; } } this.coordinates = coordinates; } public void transfigure(int dimension, double value) { double[] coordinates = new double[this.coordinates.length + 1]; int d2 = 0; for (int d = 0; d < coordinates.length; d++) { if (d == dimension) { coordinates[d] = value; }else{ coordinates[d] = this.coordinates[d2++]; } } this.coordinates = coordinates; } public Spatial getRelative(Orientation orientation) { Point point = orientation.relative(this); point.setColor(colorType); return (Spatial)point; } public Spatial getProjected(Orientation orientation) { Spatial spatial = getRelative(orientation); spatial.project(); return spatial; } public void project() { double[] coordinates = new double[this.coordinates.length - 1]; for (int d = 0; d < coordinates.length; d++) { coordinates[d] = this.coordinates[d + 1] / Math.abs(this.coordinates[0]); } this.coordinates = coordinates; } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Point point = orientation.relative(this); double[] coordinates = point.copyCoordinates(); int x = (int)point.getCoordinate(0); int y = (int)point.getCoordinate(1); graphics.setColor(colorScheme.scheme[colorType]); graphics.drawLine(x, y, x, y); } public void setColor(int colorType) { this.colorType = colorType; } public String toString() { StringBuffer str = new StringBuffer("Point"); for (int d = 0; d < coordinates.length; d++) { str.append(" " + coordinates[d] + " "); } return str.toString(); } } class Sphere extends Point { double size; Sphere(Point point, double size) { super(point.getDimensions()); sum(point); this.size = size; } double getSize() { return size; } public Spatial getRelative(Orientation orientation) { Point point = orientation.relative(this); Sphere sphere = new Sphere(point, size); sphere.setColor(colorType); return (Spatial)sphere; } // The closer you are to a sphere, the less you see of it public void project() { double tan = distance(); double sin = size; double cos = Math.sqrt(Math.pow(tan, 2) - Math.pow(sin, 2)); double angle = Math.atan(sin/cos); tan = cos; sin = tan * Math.sin(angle); cos = tan * Math.cos(angle); size = sin/cos; super.project(); } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Point point = orientation.relative(this); double[] coordinates = point.copyCoordinates(); int x = (int)point.getCoordinate(0); int y = (int)point.getCoordinate(1); Point radius = this.copyPoint(); radius.sum(0, size); radius = orientation.relative(radius); int r = Math.max(1, (int)radius.distance(point)); graphics.setColor(colorScheme.scheme[colorType]); graphics.drawOval(x - r, y - r, (int)2 * r, (int)2 * r); } public String toString() { StringBuffer str = new StringBuffer("Sphere: "); str.append(super.toString()); str.append(", size " + size); return str.toString(); } } // Alternative way of drawing a sphere (for the cursor) class Plus extends Sphere { Plus(Point point, double size) { super(point, size); } public Spatial getRelative(Orientation orientation) { Point point = orientation.relative(this); Plus plus = new Plus(point, size); plus.setColor(colorType); return (Spatial)plus; } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Point point = orientation.relative(this); double[] coordinates = point.copyCoordinates(); int x = (int)point.getCoordinate(0); int y = (int)point.getCoordinate(1); Point radius = this.copyPoint(); radius.sum(0, size); radius = orientation.relative(radius); int r = Math.max(1, (int)radius.distance(point)); graphics.setColor(colorScheme.scheme[colorType]); graphics.drawLine(x - r, y, x + r, y); graphics.drawLine(x, y - r, x, y + r); } } // Glue. class Body implements Spatial { Vector vector = new Vector(); int colorType = ColorScheme.NORMAL; // Don't add spatials twice! void add(Spatial spatial) { vector.addElement(spatial); } public void sum(int dimension, double value) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.sum(dimension, value); } } public void sum(Point point) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.sum(point); } } public void sum(Point point, double value) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.sum(point, value); } } public void multiply(double value) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.multiply(value); } } public void mirror(int dimension) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.mirror(dimension); } } public void rotate(int dimension1, int dimension2, double angle) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.rotate(dimension1, dimension2, angle); } } public void relative(Orientation orientation) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.relative(orientation); } } public void transfigure(int dimension, double value) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.transfigure(dimension, value); } } public Spatial getRelative(Orientation orientation) { Body body = new Body(); Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); body.add(spatial.getRelative(orientation)); } return (Spatial)body; } public Spatial getProjected(Orientation orientation) { Spatial spatial = getRelative(orientation); spatial.project(); return spatial; } public void project() { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.project(); } } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Enumeration e = vector.elements(); while(e.hasMoreElements()) { Spatial spatial = (Spatial)e.nextElement(); spatial.draw(orientation, colorScheme, graphics); } } public void setColor(int colorType) { this.colorType = colorType; } } class Line extends Body { Point point1, point2; Line(Point point1, Point point2) { this.point1 = point1; this.point2 = point2; add(point1); add(point2); } // Coordinate of a point if this line is ax in a coordinate system double relative(Point point) { int dimensions = point.getDimensions(); double[] position = new double[dimensions]; double[] direction = new double[dimensions]; double numerator = 0, denominator = 0, parameter; for (int d = 0; d < dimensions; d++) { // Parameter representation of the line. // y = a * x + b position[d] = point1.getCoordinate(d); direction[d] = point2.getCoordinate(d) - point1.getCoordinate(d); // Parameter for shortest distance between point and line. numerator += direction[d] * (point.getCoordinate(d) - position[d]); denominator += Math.pow(direction[d], 2); } parameter = numerator/denominator; return parameter; } Point point(double parameter) { double[] coordinates = new double[point1.getDimensions()]; for (int d = 0; d < coordinates.length; d++) { coordinates[d] = point1.getCoordinate(d) + parameter * (point2.getCoordinate(d) - point1.getCoordinate(d)); } return new Point(coordinates); } public Spatial getRelative(Orientation orientation) { Point point1 = orientation.relative(this.point1); Point point2 = orientation.relative(this.point2); Line line = new Line(point1, point2); line.setColor(colorType); return (Spatial)line; } public void project(Orientation orientation) { point1.project(); point2.project(); } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Point point1 = orientation.relative(this.point1); Point point2 = orientation.relative(this.point2); int x1 = (int)point1.getCoordinate(0); int y1 = (int)point1.getCoordinate(1); int x2 = (int)point2.getCoordinate(0); int y2 = (int)point2.getCoordinate(1); // Too hackish. if (colorType != ColorScheme.DISABLED) { graphics.setColor(colorScheme.scheme[colorType]); graphics.drawLine(x1, y1, x2, y2); } if (colorType == ColorScheme.TARGET || colorType == ColorScheme.DISABLED || colorType == ColorScheme.WINDOW) { if (colorType == ColorScheme.DISABLED) { graphics.setColor(colorScheme.scheme[ColorScheme.TARGET]); } graphics.drawOval(x1 - 2, y1 - 2, 4, 4); graphics.drawOval(x2 - 2, y2 - 2, 4, 4); } } public String toString() { StringBuffer str = new StringBuffer("Line\n"); str.append(point1.toString()); str.append("\n"); str.append(point2.toString()); return str.toString(); } } // Coordinate system. class Orientation extends Body { Point[] points; Line[] lines; int dimensions; Orientation(int dimensions) { this.dimensions = dimensions; double[] coordinates = new double[dimensions]; points = new Point[dimensions + 1]; points[0] = new Point(coordinates); add(points[0]); for (int d = 1; d < dimensions + 1; d++) { coordinates[d - 1] += 1; points[d] = new Point(coordinates); add(points[d]); coordinates[d - 1] = 0; } makeLines(); } Orientation(Point[] points) { this.points = (Point[])points.clone(); for (int p = 0; p < points.length; p++) { add(points[p]); } makeLines(); } void makeLines() { lines = new Line[points.length - 1]; for (int d = 0; d < lines.length; d++) { lines[d] = new Line(points[0], points[d + 1]); } } Point[] copyPoints() { // deep copy Point[] points = new Point[this.points.length]; for (int p = 0; p < points.length; p++) { points[p] = this.points[p].copyPoint(); } return points; } Orientation copyOrientation() { return new Orientation(copyPoints()); } double getSize() { return points[0].distance(points[1]); } void sumRelative(int dimension, double value) { double[] coordinates = new double[points.length - 1]; for (int d = 0; d < coordinates.length; d++) { coordinates[d] += (points[dimension + 1].getCoordinate(d) - points[0].getCoordinate(d)) * value; } sum(new Point(coordinates)); } void multiplyRelative(double value) { double[] coordinates = new double[points.length - 1]; for (int p = 1; p < points.length; p++) { for (int d = 0; d < coordinates.length; d++) { coordinates[d] = points[0].getCoordinate(d) + (points[p].getCoordinate(d) - points[0].getCoordinate(d)) * value; } points[p] = new Point(coordinates); } makeLines(); } boolean inside(Point point) { Point relative = relative(point); double size = points[0].distance(points[1]); return relative.inside(size); } // Return point with coordinates relative to this orientation Point relative(Point point) { double[] coordinates = new double[point.getDimensions()]; for (int d = 0; d < coordinates.length; d++) { coordinates[d] = lines[d].relative(point); } return new Point(coordinates); } void generate(double[] coordinates, int dimension, Body body) { for (int l = 0; l < lines.length; l++) { body.add(lines[l]); } } public Spatial getRelative(Orientation orientation) { Body body = new Body(); Body body2 = new Body(); generate(new double[dimensions], 0, body2); body.add(body2.getRelative(orientation)); return (Spatial)body; } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Body body = new Body(); generate(new double[dimensions], 0, body); body.draw(orientation, colorScheme, graphics); } public String toString() { StringBuffer str = new StringBuffer("Orientation\n"); for (int p = 0; p < points.length; p++) { str.append(points[p] + "\n"); } return str.toString(); } } class Cube extends Orientation { Cube(int dimensions) { super(dimensions); } // Can't nest an arbitrary number of loops, // so use recursion to generate points and lines. // Points and lines will be relative to this orientation void generate(double[] coordinates, int dimension, Body body) { create(coordinates, dimension, -1, body); create(coordinates, dimension, +1, body); } void create(double[] coordinates, int dimension, int sign, Body body) { coordinates[dimension] = sign; if (dimension < coordinates.length - 1) { generate(coordinates, ++dimension, body); }else{ //Point point = new Point(coordinates); //point.relative(this); //System.out.println(point); //body.add(point); //Sphere sphere = new Sphere(point, 0.15 / getSize()); //body.add(sphere); line(coordinates, body); } } void line(double[] coordinates, Body body) { double[] coordinates2 = new double[coordinates.length]; for (int d = 0; d < coordinates.length; d++) { if (coordinates[d] < 0) { // No double lines System.arraycopy(coordinates, 0, coordinates2, 0, coordinates.length); coordinates2[d] = -coordinates[d]; Point point1 = new Point(coordinates); Point point2 = new Point(coordinates2); point1.relative(this); point2.relative(this); Line line = new Line(point1, point2); //System.out.println(line); line.setColor(colorType); body.add(line); } } } } // Alternative way of drawing a cube. class Cross extends Cube { Cross(int dimensions) { super(dimensions); } void line(double[] coordinates, Body body) { double[] coordinates2 = new double[coordinates.length]; for (int d = 0; d < coordinates.length; d++) { if (coordinates[d] < 0) { // No double lines System.arraycopy(coordinates, 0, coordinates2, 0, coordinates.length); for (int d2 = 0; d2 < coordinates.length; d2++) { coordinates2[d2] = -coordinates[d2]; } Point point1 = new Point(coordinates); Point point2 = new Point(coordinates2); point1.relative(this); point2.relative(this); Line line = new Line(point1, point2); //System.out.println(line); line.setColor(colorType); body.add(line); } } } } // Windows and targets on cubes. class Spot extends Body { int dimensions; double size; Orientation orientation; int dimension; int side; Point point; Spot(Orientation orientation, double size, int dimension, int side) { this.orientation = orientation; // cube orientation this.size = size; add(orientation); this.dimensions = orientation.dimensions; random(dimension, side); } Spot(Orientation orientation, double size, int dimension, int side, Point point) { this.orientation = orientation; this.size = size; this.dimension = dimension; this.side = side; add(orientation); this.point = point.copyPoint(); this.point.transfigure(dimension); this.dimensions = orientation.dimensions; } void random(int dimension, int side) { this.dimension = dimension; this.side = side; point = new Point(dimensions - 1); for (int d = 0; d < point.getDimensions(); d++) { point.sum(d, Math.random() / (2 - size) - (1 - 2 * size)); } } boolean inside(Point point) { Point center = this.point.copyPoint(); center.transfigure(dimension, side); center.relative(orientation); boolean inside = true; for (int d = 0; d < point.getDimensions(); d++) { if (Math.abs(point.getCoordinate(d) - center.getCoordinate(d)) > size) { inside = false; } } return inside; } } // The cursor can move through this window. // Note: the ball bounces off it. class Window extends Spot { Window(Orientation orientation, double size, int dimension, int side) { super(orientation, size, dimension, side); colorType = ColorScheme.WINDOW; } public Spatial getRelative(Orientation orientation) { Body body = new Body(); //Point point = this.point.copyPoint(); //point.transfigure(dimension, side); //point.relative(this.orientation); //body.add(point.project(orientation)); Cube cube = new Cube(dimensions - 1); cube.multiplyRelative(1/size); cube.setColor(colorType); Body body2 = new Body(); double[] coordinates = new double[dimensions - 1]; cube.generate(coordinates, 0, body2); body2.sum(this.point); body2.transfigure(dimension, side); body2.relative(this.orientation); body.add(body2.getRelative(orientation)); return (Spatial)body; } } // Target for the ball. class Target extends Spot { boolean enabled; Space space; Target(Orientation orientation, double size, int dimension, int side, Space space) { super(orientation, size, dimension, side); enabled = true; this.space = space; } Target(Orientation orientation, double size, int dimension, int side, Point point, Space space) { super(orientation, size, dimension, side, point); this.space = space; } void random(int dimension, int side) { super.random(dimension, side); enabled = true; } boolean inside(Point point) { if (!enabled) { return false; } boolean returnValue = super.inside(point); if (returnValue) { //System.out.println("Ball hit target!"); space.hit.play(); if (space.score.hit()) { space.gameover.play(); } enabled = false; } return returnValue; } public void disable() { enabled = false; } public Spatial getRelative(Orientation orientation) { Body body = new Body(); //Point point = this.point.copyPoint(); //point.transfigure(dimension, side); //point.relative(this.orientation); //body.add(point.project(orientation)); Cross cross = new Cross(dimensions - 1); cross.multiplyRelative(1/size); if (enabled) { cross.setColor(ColorScheme.TARGET); }else{ cross.setColor(ColorScheme.DISABLED); } Body lines = new Body(); double[] coordinates = new double[dimensions - 1]; cross.generate(coordinates, 0, lines); lines.sum(this.point); lines.transfigure(dimension, side); lines.relative(this.orientation); body.add(lines.getRelative(orientation)); return (Spatial)body; } } // The Ball gets kicked by the Cursor, bounces off the cube and the // cursor. class Ball extends Sphere { Point movement; boolean drawLines = false; Point bouncePoint; Point centerPoint; Body centerLines; Body bounceLines; Body counterLines; Body bounceCrosses; Space space; Ball(Point point, double size, Space space) { super(point, size); initBodies(); movement = new Point(space.dimensions); this.space = space; } private void initBodies() { centerLines = new Body(); bounceLines = new Body(); counterLines = new Body(); bounceCrosses = new Body(); } boolean kick(Point oldPosition, Point newPosition) { if (distance(newPosition) < size) { movement = difference(newPosition); // Kick with constant force movement.multiply(2 * 0.05 / movement.distance()); // Variabe force: oldPosition.distance(newPosition) centerPoint = copyPoint(); bouncePoint = copyPoint(); initBodies(); Line line = new Line(oldPosition.copyPoint(), this.copyPoint()); line.setColor(ColorScheme.BOUNCE); centerLines.add(line); drawLines = true; return true; }else{ return false; } } void update(Orientation cube, Cursor cursor, Target target) { Point oldPosition = copyPoint(); Point newPosition = copyPoint(); newPosition.sum(movement); // Bounce of a side or the cursor, whichever is closest. Point relative = cube.relative(newPosition); Point centerPoint = null; // center of ball when it bounces Point bouncePoint = null; // where the ball touches when it bounces Target bounceCross = null; double distance; double minimumDistance = 1; // Cube for (int d = 0; d < coordinates.length; d++) { distance = Math.abs(1 - Math.abs(relative.coordinates[d])); if (distance < size) { if (distance < minimumDistance) { minimumDistance = distance; bouncePoint = relative.copyPoint(); bouncePoint.coordinates[d] = Math.round(bouncePoint.coordinates[d]); Orientation orientation = new Orientation(coordinates.length); bouncePoint = orientation.relative(bouncePoint); centerPoint = relative.copyPoint(); int side = (int)bouncePoint.coordinates[d]; bounceCross = new Target(cube, 0.05, d, side, bouncePoint, space); } } } // Cursor Point cursorPosition = cursor.copyOrientation().relative(new Point(space.dimensions)); distance = newPosition.distance(cursorPosition); if (distance < size) { if (distance < minimumDistance) { minimumDistance = distance; bouncePoint = cursorPosition.copyPoint(); centerPoint = relative.copyPoint(); } } if (bouncePoint != null) { Point counterMovement = centerPoint.difference(bouncePoint); Line line = new Line(bouncePoint, centerPoint); line.setColor(ColorScheme.BOUNCE); counterLines.add(line); double parameter = line.relative(this); Point projectedPoint = line.point(parameter); double difference = centerPoint.distance(projectedPoint); counterMovement.multiply(2 * difference / counterMovement.distance()); movement.sum(counterMovement); Line centerLine = new Line(this.centerPoint, centerPoint); Line bounceLine = new Line(this.bouncePoint, bouncePoint); centerLine.setColor(ColorScheme.TRACK); bounceLine.setColor(ColorScheme.TRACK); centerLines.add(centerLine); bounceLines.add(bounceLine); this.centerPoint = centerPoint; this.bouncePoint = bouncePoint; if (bounceCross != null) { bounceCross.setColor(ColorScheme.NORMAL); bounceCrosses.add(bounceCross); if (target.inside(bouncePoint)) { //System.out.println("Ball hit (possibly disabled) target"); } } space.bounce.play(); } sum(movement); movement.multiply(0.98); // decrease speed } void dontDrawLines() { drawLines = false; } public Spatial getRelative(Orientation orientation) { Body body = new Body(); if (drawLines) { Line line = new Line(centerPoint, this.copyPoint()); //Line line = new Line(bouncePoint, this.copyPoint()); line.setColor(ColorScheme.BOUNCE); body.add(line.getRelative(orientation)); //body.add(bounceLines.getRelative(orientation)); body.add(centerLines.getRelative(orientation)); //body.add(bounceCrosses.getRelative(orientation)); body.add(counterLines.getRelative(orientation)); } body.add(super.getRelative(orientation)); return (Spatial)body; } } // Walk around the cube and catch the cursor. class Guard extends Sphere { int steps = 100; double range = Space.MAXCOORDINATE; int step = 0; int dimension = 0; int direction = -1; Line line; boolean drawLine = false; Space space; Guard(Point point, double size, Space space) { super(point, size); this.space = space; } void update(Cursor cursor) { boolean remDrawLine = drawLine; step = ++step; if (step > steps) { step = 0; if (dimension == 0) { dimension = 1; }else{ dimension = 0; if (direction == -1) { direction = 1; }else{ direction = -1; } } } this.coordinates[dimension] = (1 - 2 * dimension) * direction * range; this.coordinates[1 - dimension] = direction * range - step * 2 * range * direction / steps; Point point = cursor.copyOrientation().relative(new Point(getDimensions())); line = new Line(point, this); line.setColor(ColorScheme.ERROR); remDrawLine = drawLine; drawLine = false; for (int d = 0; d < getDimensions(); d++) { if (((this.getCoordinate(d) > 1) && (point.getCoordinate(d) > 1)) || ((this.getCoordinate(d) < -1) && (point.getCoordinate(d) < -1))) { drawLine = true; if (!remDrawLine) { space.gotcha.play(); } space.score.gotcha(); } } } public Spatial getRelative(Orientation orientation) { Body body = new Body(); if (drawLine) { body.add(line.getRelative(orientation)); } body.add(super.getRelative(orientation)); return (Spatial)body; } } // Player position. class Cursor extends Orientation { final double size = 0.1; // Cursor tail on window-pass/cube-bump boolean drawTail = true; boolean lastChangeIsMove = false; double lastMove = 0; Point arrowTail, arrowHead, newTail; Line arrow; Cursor(int dimensions) { super(dimensions); arrowTail = copyOrientation().relative(new Point(dimensions)); arrowHead = copyOrientation().relative(new Point(dimensions)); newTail = copyOrientation().relative(new Point(dimensions)); arrow = new Line(arrowTail, arrowHead); } Cursor(Point[] points) { super(points); } Cursor copyCursor() { return new Cursor(copyPoints()); } // Move forward/backwards, as viewed from a distance. void move(double value, double viewDistance) { if (viewDistance == 0) { // dimensions < 3 sum(0, value); }else{ Orientation orientation = copyOrientation(); orientation.sum(0, viewDistance); Point direction = orientation.copyPoints()[0]; sum(direction, value); } if ((lastMove >= 0 && value < 0) || (lastMove <= 0 && value > 0)) { changeTail(); } if (!lastChangeIsMove) { drawTail = false; } lastMove = value; lastChangeIsMove = true; } public void relative(Orientation orientation) { changeTail(); super.relative(orientation); } void changeTail() { if (lastChangeIsMove) { newTail = copyOrientation().relative(new Point(dimensions)); lastChangeIsMove = false; } } void setArrow() { arrowTail = newTail; arrowHead = copyOrientation().relative(new Point(dimensions)); arrow = new Line(arrowTail, arrowHead); arrow.setColor(ColorScheme.OK); drawTail = true; } void setArrowColor(int colorType) { arrow.setColor(colorType); } public Spatial getRelative(Orientation orientation) { Body body = new Body(); Point point = copyOrientation().relative(new Point(dimensions)); Plus plus = new Plus(point, 0.1); plus.setColor(colorType); body.add(plus.getRelative(orientation)); if (drawTail) { //Sphere head = new Sphere(arrowHead, 0.05); //body.add(head.getRelative(orientation)); body.add(arrow.getRelative(orientation)); } return (Spatial)body; } public void draw(Orientation orientation, ColorScheme colorScheme, Graphics graphics) { Point point = copyOrientation().relative(new Point(dimensions)); Sphere sphere = new Sphere(point, 0.1); sphere.setColor(colorType); sphere.draw(orientation, colorScheme, graphics); } } class ColorScheme { // color scheme index static int BACKGROUND = 0; static int NORMAL = 1; static int ERROR = 2; static int OK = 3; static int MARK = 4; static int DISABLED = 5; static int WINDOW = 6; static int TARGET= 7; static int TRACK = 8; static int BOUNCE = 9; Color[] scheme = new Color[10]; Color[] filter(int position, int green) { Color[] newScheme = new Color[10]; // background newScheme[0] = new Color(scheme[0].getRed(), scheme[0].getGreen(), scheme[0].getBlue()); // foreground switch (position) { case 0: // green or blue for (int index = 1; index < 10; index++) { newScheme[index] = new Color(0, scheme[index].getGreen(), scheme[index].getBlue()); } break; case 2: // red for (int index = 1; index < 10; index++) { newScheme[index] = new Color(scheme[index].getRed(), scheme[index].getGreen() * green, 0); } break; default: // unaltered colors newScheme = scheme; } return newScheme; } } class Score { int factor = 1000; // milliseconds int fps = 20; // frames per second int numHits = 5; int points = 100 * factor; int hits = 0; void update() { if (hits < numHits) { points = points - 1 * factor / fps; } } void bump() { if (hits < numHits) { points = points - 10 * factor; } } void gotcha() { if (hits < numHits) { points = points - 5 * factor / fps; } } boolean hit() { if (hits < numHits) { points = points + 100 * factor; hits = hits + 1; if (hits == numHits) { return true; } } return false; } public String toString() { if (hits == numHits) { return new String(hits + "/" + points / factor +" done."); } return new String(hits + "/" + points / factor); } } class Sound { AudioClip clip; boolean enabled = false;; Sound(AudioClip clip) { this.clip = clip; } void enable(boolean value) { enabled = value; } void play() { if (clip != null && enabled) { clip.play(); } } } public class Space extends Canvas implements ItemListener, Runnable{ final static double MAXCOORDINATE = 1.25; int dimensions; Applet applet; Frame frame; Panel containerPanel; Panel controlPanel; Choice colorChoice; Choice stereoChoice; Checkbox detachBox, swapBox, soundBox; boolean stereo = true; int swap = -1; int notAnaglyph = 0; boolean detach = false; boolean sound = false; ColorScheme[] colorScheme, whiteScheme, blackScheme, grayScheme, greenScheme, blueScheme; boolean running = true; boolean focus = false; boolean pauzed = false; long lostFocusTime; Body[][] scene; Sphere playfield; Cursor cursor; Cube cube; Window window; Target target; Ball ball; Guard guard; double viewDistance = 2; double eyeDistance = 0.1; double cursorLock = 0; int lastX, lastY; double unit, unit2; Dimension offDimension; Image offImage; Graphics offGraphics; Sound bump, kick, bounce, pass, hit, gotcha, gameover; Score score = new Score(); Space(int dimensions, String stereoString, boolean paramSwap, boolean sound, final Applet applet) { this.dimensions = dimensions; this.sound = sound; if (paramSwap) { swap = 1; }else{ swap = -1; } this.applet = applet; bump = new Sound(Global.bump); kick = new Sound(Global.kick); bounce = new Sound(Global.bounce); pass = new Sound(Global.pass); hit = new Sound(Global.hit); gotcha = new Sound(Global.gotcha); gameover = new Sound(Global.gameover); toggleSound(sound); cursor = new Cursor(dimensions); cube = new Cube(dimensions); window = new Window(cube, 0.2, 1, 1); target = new Target(cube, 0.3, 1, -1, this); ball = new Ball(new Point(dimensions), 0.3, this); for (int d = 0; d < dimensions; d++) { ball.sum(d, -0.5); } guard = new Guard(new Point(dimensions), 0.1, this); guard.sum(0, -1.25); guard.sum(1, -1.25); Point score = new Point(dimensions); score.sum(dimensions - 1, -1.25); scene = new Body[dimensions + 1][3]; scene[dimensions][1] = new Body(); scene[dimensions][1].add(cursor); scene[dimensions][1].add(cube); scene[dimensions][1].add(window); scene[dimensions][1].add(target); scene[dimensions][1].add(ball); scene[dimensions][1].add(guard); playfield = new Sphere(new Point(dimensions), Math.sqrt(dimensions * Math.pow(MAXCOORDINATE, 2))); //scene[dimensions][1].add(playfield); containerPanel = new Panel(); containerPanel.setLayout(new BorderLayout()); controlPanel = new Panel(); controlPanel.setLayout(new FlowLayout()); whiteScheme = new ColorScheme[3]; whiteScheme[1] = new ColorScheme(); whiteScheme[1].scheme[ColorScheme.BACKGROUND] = Color.white; whiteScheme[1].scheme[ColorScheme.NORMAL] = Color.black; whiteScheme[1].scheme[ColorScheme.ERROR] = Color.red; whiteScheme[1].scheme[ColorScheme.OK] = Color.green; whiteScheme[1].scheme[ColorScheme.MARK] = Color.blue; whiteScheme[1].scheme[ColorScheme.DISABLED] = Color.lightGray; whiteScheme[1].scheme[ColorScheme.WINDOW] = Color.green; whiteScheme[1].scheme[ColorScheme.TARGET] = Color.blue; whiteScheme[1].scheme[ColorScheme.TRACK] = Color.blue; whiteScheme[1].scheme[ColorScheme.BOUNCE] = Color.cyan; whiteScheme[0] = new ColorScheme(); whiteScheme[2] = new ColorScheme(); whiteScheme[0].scheme = whiteScheme[1].filter(0, 1); whiteScheme[2].scheme = whiteScheme[1].filter(2, 1); //unused blackScheme = new ColorScheme[3]; blackScheme[1] = new ColorScheme(); blackScheme[1].scheme[ColorScheme.BACKGROUND] = Color.black; blackScheme[1].scheme[ColorScheme.NORMAL] = Color.lightGray; blackScheme[1].scheme[ColorScheme.ERROR] = Color.red; blackScheme[1].scheme[ColorScheme.OK] = Color.green; blackScheme[1].scheme[ColorScheme.MARK] = Color.yellow; blackScheme[1].scheme[ColorScheme.DISABLED] = Color.gray; blackScheme[1].scheme[ColorScheme.WINDOW] = Color.lightGray; blackScheme[1].scheme[ColorScheme.TARGET] = Color.yellow; blackScheme[1].scheme[ColorScheme.TRACK] = Color.gray; blackScheme[1].scheme[ColorScheme.BOUNCE] = Color.lightGray; blackScheme[0] = new ColorScheme(); blackScheme[2] = new ColorScheme(); blackScheme[0].scheme = blackScheme[1].filter(0, 1); blackScheme[2].scheme = blackScheme[1].filter(2, 1); // Unused grayScheme = new ColorScheme[3]; grayScheme[1] = new ColorScheme(); grayScheme[1].scheme[ColorScheme.BACKGROUND] = Color.lightGray; grayScheme[1].scheme[ColorScheme.NORMAL] = Color.black; grayScheme[1].scheme[ColorScheme.ERROR] = Color.red; grayScheme[1].scheme[ColorScheme.OK] = Color.yellow; grayScheme[1].scheme[ColorScheme.MARK] = Color.white; grayScheme[1].scheme[ColorScheme.DISABLED] = Color.gray; grayScheme[1].scheme[ColorScheme.WINDOW] = Color.black; grayScheme[1].scheme[ColorScheme.TARGET] = Color.black; grayScheme[1].scheme[ColorScheme.TRACK] = Color.white; grayScheme[1].scheme[ColorScheme.BOUNCE] = Color.yellow; grayScheme[0] = new ColorScheme(); grayScheme[2] = new ColorScheme(); grayScheme[0].scheme = grayScheme[1].filter(0, 1); grayScheme[2].scheme = grayScheme[1].filter(2, 1); // Anaglyphs can be monochrome and colored: // http://en.wikipedia.org/wiki/Anaglyph_image // If it can be done by using primitive graphics animation too, // remains to be seen... // For now I copied the colors from // http://dogfeathers.com/java/hyprcube.html // but unlike Mark Newbold, I like red/green glasses best. Color background = new Color(222, 222, 222); Color foreground = new Color (255, 239, 0); greenScheme = new ColorScheme[3]; greenScheme[1] = new ColorScheme(); greenScheme[1].scheme[ColorScheme.BACKGROUND] = background; greenScheme[1].scheme[ColorScheme.NORMAL] = foreground; greenScheme[1].scheme[ColorScheme.ERROR] = foreground; greenScheme[1].scheme[ColorScheme.OK] = foreground; greenScheme[1].scheme[ColorScheme.MARK] = foreground; greenScheme[1].scheme[ColorScheme.DISABLED] = foreground; greenScheme[1].scheme[ColorScheme.WINDOW] = foreground; greenScheme[1].scheme[ColorScheme.TARGET] = foreground; greenScheme[1].scheme[ColorScheme.TRACK] = foreground; greenScheme[1].scheme[ColorScheme.BOUNCE] = foreground; greenScheme[0] = new ColorScheme(); greenScheme[2] = new ColorScheme(); greenScheme[0].scheme = greenScheme[1].filter(swap * 1 + 1, 0); greenScheme[2].scheme = greenScheme[1].filter(swap * -1 + 1, 0); background = new Color(0, 84, 0); foreground = new Color (188, 84, 255); blueScheme = new ColorScheme[3]; blueScheme[1] = new ColorScheme(); blueScheme[1].scheme[ColorScheme.BACKGROUND] = background; blueScheme[1].scheme[ColorScheme.NORMAL] = foreground; blueScheme[1].scheme[ColorScheme.ERROR] = foreground; blueScheme[1].scheme[ColorScheme.OK] = foreground; blueScheme[1].scheme[ColorScheme.MARK] = foreground; blueScheme[1].scheme[ColorScheme.DISABLED] = foreground; blueScheme[1].scheme[ColorScheme.WINDOW] = foreground; blueScheme[1].scheme[ColorScheme.TARGET] = foreground; blueScheme[1].scheme[ColorScheme.TRACK] = foreground; blueScheme[1].scheme[ColorScheme.BOUNCE] = foreground; blueScheme[0] = new ColorScheme(); blueScheme[2] = new ColorScheme(); blueScheme[0].scheme = blueScheme[1].filter(swap * -1 + 1, 1); blueScheme[2].scheme = blueScheme[1].filter(swap * 1 + 1, 1); colorScheme = whiteScheme; // Unused colorChoice = new Choice(); colorChoice.addItem("white"); colorChoice.addItem("black"); colorChoice.addItem("gray"); colorChoice.addItem("white"); colorChoice.addItem("green"); colorChoice.addItem("blue"); colorChoice.select("white"); //controlPanel.add(colorChoice); colorChoice.addItemListener((ItemListener)this); stereoSelect(stereoString); stereoChoice = new Choice(); stereoChoice.addItem("normal"); stereoChoice.addItem("cross eyed"); stereoChoice.addItem("anaglyph green"); stereoChoice.addItem("anaglyph blue"); stereoChoice.select(stereoString); controlPanel.add(stereoChoice); stereoChoice.addItemListener((ItemListener)this); swapBox = new Checkbox("swap"); swapBox.setState(false); controlPanel.add(swapBox); swapBox.addItemListener((ItemListener)this); soundBox = new Checkbox("sound"); soundBox.setState(sound); controlPanel.add(soundBox); soundBox.addItemListener((ItemListener)this); containerPanel.add("Center", this); containerPanel.add("North", controlPanel); frame = new Frame(new String(dimensions + "d-Cube")); frame.setSize(600, 400); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { if (applet == null) { running = false; resume(); if (Global.applet == null){ System.exit(0); } }else{ toggleDetached(); } } }); if (applet != null) { applet.removeAll(); detachBox = new Checkbox("detach"); controlPanel.add(detachBox); detachBox.addItemListener((ItemListener)this); applet.setLayout(new BorderLayout()); applet.add("Center", containerPanel); }else{ frame.add(containerPanel); frame.show(); } addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { //System.out.println(e); keyPress(e); } }); addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { //System.out.println(e); keyRelease(e); } }); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { //System.out.println(e); requestFocus(); mousePress(e); } }); addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent e) { //System.out.println(e); requestFocus(); mouseRelease(e); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { //System.out.println(e); mouseDrag(e); } }); addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent e) { //System.out.println(e); focus = true; resume(); } public void focusLost(FocusEvent e) { //System.out.println(e); focus = false; lostFocusTime = System.currentTimeMillis(); } }); // Click to start when embedded. if (applet == null) { requestFocus(); } new Thread(this).start(); gameover.play(); } synchronized void resume(){ notifyAll(); } synchronized void pauze() { while ((!focus || pauzed) && running) { try{ wait(); }catch(InterruptedException e){} } } public void run() { long previousTimer, timer = System.currentTimeMillis(), runTime, frameTime = 50, sleepTime = 0; running = true; while(running) { previousTimer = timer; timer = System.currentTimeMillis(); runTime = timer - previousTimer - sleepTime; sleepTime = Math.max(0, frameTime - runTime); //System.out.println(runTime + " " + sleepTime); try { // Pauze on applet-stop and lost-focus-with-delay if ((!focus && (timer - lostFocusTime > 10000)) || pauzed) { pauze(); }else{ Thread.sleep(sleepTime); } }catch(InterruptedException e){} step(); repaint(); } frame.dispose(); //System.out.println("Thread stopped."); } void stereoSelect(String str) { if (str.equals("normal")){ stereo = false; }else{ stereo = true; } if (str.indexOf("anaglyph") != -1) { notAnaglyph = 0; if (str.indexOf("green") != -1) { colorScheme = greenScheme; } if (str.indexOf("blue") != -1) { colorScheme = blueScheme; } }else{ notAnaglyph = 1; colorScheme = whiteScheme; } } public void itemStateChanged(ItemEvent e) { //System.out.println(e); Object source = e.getItemSelectable(); // unused if (source == colorChoice) { if (colorChoice.getSelectedItem().equals("white")) { colorScheme = whiteScheme; } if (colorChoice.getSelectedItem().equals("black")) { colorScheme = blackScheme; } if (colorChoice.getSelectedItem().equals("gray")) { colorScheme = grayScheme; } if (colorChoice.getSelectedItem().equals("green")) { colorScheme = greenScheme; } if (colorChoice.getSelectedItem().equals("blue")) { colorScheme = blueScheme; } } if (source == stereoChoice) { stereoSelect(stereoChoice.getSelectedItem()); } if (source == swapBox) { if (e.getStateChange() == ItemEvent.DESELECTED) { swap = -1; } if (e.getStateChange() == ItemEvent.SELECTED) { swap = 1; } greenScheme[0].scheme = greenScheme[1].filter(swap * 1 + 1, 0); greenScheme[2].scheme = greenScheme[1].filter(swap * -1 + 1, 0); blueScheme[0].scheme = blueScheme[1].filter(swap * -1 + 1, 1); blueScheme[2].scheme = blueScheme[1].filter(swap * 1 + 1, 1); } if (source == soundBox) { if (e.getStateChange() == ItemEvent.DESELECTED) { sound = false; } if (e.getStateChange() == ItemEvent.SELECTED) { sound = true; } toggleSound(sound); } if (source == detachBox) { toggleDetached(); } repaint(); } void mousePress(MouseEvent e) { lastX = e.getX(); lastY = e.getY(); } // Angle of a mousemove that takes a stereoscopic canvas into account double angle(MouseEvent e) { Dimension dimension = getSize(); int x = e.getX(); int y = e.getY(); int x1 = lastX; int x2 = x; double width = dimension.width; if (stereo && notAnaglyph == 1) { width /= 2; if (x > width) { x1 -= width; x2 -= width; } } double[] coordinates = new double[2]; coordinates[0] = x1 - width / 2; coordinates[1]= lastY - dimension.height / 2; Point point1 = new Point(coordinates); double angle1 = point1.angle(0, 1); coordinates[0] = x2 - width / 2; coordinates[1]= y - dimension.height / 2; Point point2 = new Point(coordinates); double angle2 = point2.angle(0, 1); return angle2 - angle1; } void mouseDrag(MouseEvent e) { Dimension dimension = getSize(); double x = (e.getX() - lastX) / unit * Math.PI / 2; double y = (e.getY() - lastY) / unit * Math.PI / 2; double angle = angle(e); Orientation orientation = new Orientation(dimensions); int getModResult = 0; try { getModResult = ((Integer)Global.getMod.invoke(e, new Object[0])).intValue(); }catch(Exception e2){ e2.printStackTrace(); } if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) { move(-(e.getY() - lastY) / unit2); }else if ((getModResult & Global.button2) != 0) { if (dimensions > 3) { //orientation.rotate(0, 3, angle); orientation.rotate(0, 3, -y); } cursor.relative(orientation); }else if ((getModResult & Global.button3) != 0) { if (dimensions > 3) { orientation.rotate(3, 1, -x); orientation.rotate(3, 2, y); }else{ orientation.rotate(1, 2, angle); } cursor.relative(orientation); }else{ if (dimensions > 2) { orientation.rotate(0, 1, -x); orientation.rotate(0, 2, y); }else{ orientation.rotate(0, 1, angle); } cursor.relative(orientation); } lastX = e.getX(); lastY = e.getY(); repaint(); } void mouseRelease(MouseEvent e) { cursorLock = 0; } void keyPress(KeyEvent e) { Orientation orientation = new Orientation(dimensions); int dimension1 = 0; int dimension2 = 0; double value = 0; if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) { value = 0.05; switch (e.getKeyCode()) { case KeyEvent.VK_UP: break; case KeyEvent.VK_DOWN: value = -value; break; default: value = 0; } move(value); } else if (((e.getModifiers() & InputEvent.META_MASK) != 0) || ((e.getModifiers() & InputEvent.ALT_MASK) != 0)) { value = -2 * Math.PI / 128; dimension1 = 0; dimension2 = 3; switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: value = -value; case KeyEvent.VK_RIGHT: break; case KeyEvent.VK_UP: value = -value; case KeyEvent.VK_DOWN: break; default: value = 0; } if (dimensions > 3) { orientation.rotate(dimension1, dimension2, value); cursor.relative(orientation); } }else if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) { value = - 2 * Math.PI / 128; dimension1 = dimensions - 1; switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: value = -value; case KeyEvent.VK_RIGHT: dimension2 = Math.min(1, dimensions - 2); break; case KeyEvent.VK_DOWN: value = -value; case KeyEvent.VK_UP: dimension2 = Math.min(2, dimensions - 2); break; default: value = 0; } orientation.rotate(dimension1, dimension2, value); cursor.relative(orientation); }else{ value = -2 * Math.PI / 128; switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: value = -value; case KeyEvent.VK_RIGHT: dimension2 = 1; if (dimensions == 2) { value = - value; } break; case KeyEvent.VK_DOWN: value = -value; case KeyEvent.VK_UP: dimension2 = 2; if (dimensions == 2) { value = -value; dimension2 = 1; } break; case KeyEvent.VK_PAGE_UP: viewDistance = viewDistance - 0.02; if (viewDistance < MAXCOORDINATE) { viewDistance = MAXCOORDINATE; } value = 0; break; case KeyEvent.VK_PAGE_DOWN: viewDistance = viewDistance + 0.02; value = 0; break; case KeyEvent.VK_HOME: eyeDistance = eyeDistance - 0.002; if (eyeDistance < 0) { eyeDistance = 0; } value = 0; //System.out.println(eyeDistance); break; case KeyEvent.VK_END: eyeDistance = eyeDistance + 0.002; value = 0; //System.out.println(eyeDistance); break; default: value = 0; } orientation.rotate(dimension1, dimension2, value); cursor.relative(orientation); } repaint(); } void toggleSound(boolean sound) { bump.enable(sound); kick.enable(sound); bounce.enable(sound); pass.enable(sound); gotcha.enable(sound); hit.enable(sound); gameover.enable(sound); } void toggleDetached() { if (detach) { applet.setLayout(new BorderLayout()); applet.add("Center", containerPanel); applet.validate(); applet.doLayout(); frame.dispose(); detach = false; }else{ applet.remove(containerPanel); frame.add(containerPanel); frame.show(); detach = true; } detachBox.setState(detach); } void keyRelease(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_SHIFT) { cursorLock = 0; } } public Dimension getPreferredSize() { return getParent().getSize(); } public void paint(Graphics graphics) { update(graphics); } void move(double value) { if ((value > 0 && cursorLock < 0) || (value < 0 && cursorLock > 0)) { cursorLock = 0; } if (cursorLock != 0 || value == 0.0) { return; } cursor.setColor(ColorScheme.NORMAL); ball.dontDrawLines(); // if (Math.abs(value) > 0.05) { // System.out.println("too fast: " + value); // } if (value > 0.05) { value = 0.05; } if (value < -0.05) { value = -0.05; } Cursor scratch = cursor.copyCursor(); Point oldPosition = scratch.copyOrientation().relative(new Point(dimensions)); double perspective = viewDistance * playfield.getSize(); if (dimensions < 3) { perspective = 0; } scratch.move(value, perspective); Point newPosition = scratch.copyOrientation().relative(new Point(dimensions)); if (newPosition.distance() > playfield.getSize()) { //System.out.println("Too far! (Playfield boundary)"); cursor.setColor(ColorScheme.MARK); cursorLock = value; } if (cube.inside(oldPosition) != cube.inside(newPosition)) { cursor.setArrow(); if (window.inside(oldPosition)) { //System.out.println("Passed window"); cursor.setArrowColor(ColorScheme.OK); pass.play(); // Move window and target (if it is disabled). int dimension, side; do { dimension = (int)(Math.random() * dimensions); side = (int)(Math.round(Math.random()) * 2 - 1); }while ((dimension == window.dimension && side == window.side) || (dimension == target.dimension && side == target.side)); int oldDimension = window.dimension; int oldSide = window.side; window.random(dimension, side); if (!target.enabled) { do { dimension = (int)(Math.random() * dimensions); side = (int)(Math.round(Math.random()) * 2 - 1); }while ((dimension == oldDimension && side == oldSide) || (dimension == window.dimension && side == window.side) || (dimension == target.dimension && side == target.side)); target.random(dimension, side); } }else{ //System.out.println("Ouch! (Cube boundary)"); cursor.setColor(ColorScheme.ERROR); cursor.setArrowColor(ColorScheme.ERROR); cursorLock = value; bump.play(); score.bump(); } } if (ball.kick(oldPosition, newPosition)) { //System.out.println("Kick! (Ball boundary)"); cursor.setColor(ColorScheme.MARK); cursorLock = value; kick.play(); } if (cursorLock == 0) { cursor.move(value, perspective); } } public void step() { ball.update(cube, cursor, target); guard.update(cursor); score.update(); } public void update(Graphics graphics) { Dimension dimension = getSize(); Color color = null; // Create the offscreen graphics context, if no good one exists. if ((offGraphics == null) || (dimension.width != offDimension.width) || (dimension.height != offDimension.height)) { offDimension = dimension; offImage = createImage(dimension.width, dimension.height); offGraphics = offImage.getGraphics(); } offGraphics.setColor(colorScheme[notAnaglyph * 1].scheme[ColorScheme.BACKGROUND]); //offGraphics.setColor(colorScheme[1].scheme[ColorScheme.BACKGROUND]); offGraphics.fillRect(0, 0, dimension.width, dimension.height); Orientation orientation = new Orientation(dimensions); Orientation cursorOrientation = cursor.copyOrientation(); orientation.sum(cursorOrientation.copyPoints()[0]); orientation.relative(cursorOrientation); Sphere playfield = new Sphere(new Point(dimensions), Math.sqrt(dimensions * Math.pow(MAXCOORDINATE, 2))); double playfieldSize = playfield.getSize(); // for unit2 Sphere[] playfieldY = new Sphere[3]; int maxY = 1; if (!stereo) { maxY = 0; } if (dimensions == 2) { // Don't project, just add relative image to scene. for (int y = -1 * maxY; y <= 1 * maxY; y++) { scene[dimensions - 1][y + 1] = new Body(); scene[dimensions - 1][y + 1].add(scene[dimensions][1].getRelative(orientation)); scene[dimensions - 1][y + 1].rotate(0, 1, - Math.PI / 2); } }else{ scene[dimensions - 1][1] = scene[dimensions][1]; } for (int d = dimensions - 1; d > 1; d--) { // Project to n-1 space // View from a distance orientation.sumRelative(0, viewDistance * playfield.getSize()); // Only calculate 3d->2d stereoscopic images int dimY = 0; if (d == 2) { dimY = 1; } // mono & stereo views for (int y = -1 * maxY * dimY ; y <= 1 * maxY * dimY; y++) { Orientation orientationY = orientation.copyOrientation(); orientationY.sumRelative(1, y * eyeDistance * playfield.getSize()); scene[d - 1][y + 1] = new Body(); scene[d - 1][y + 1].add(scene[d][1].getProjected(orientationY)); playfieldY[y + 1] = (Sphere)playfield.getProjected(orientationY); } // Next projection round if (d > 1) { // View the projected image from within orientation = new Orientation(d); // Let the last dimension be the first, // so the others retain their familiar meaning // in the projected image. // (Does this actually work for dimensions > 4?) for(int d2 = 0; d2 < d - 1; d2++) { orientation.rotate(d2, d2 + 1, - Math.PI / 2); } playfield = playfieldY[1]; } } // Adjust to canvas size and draw double height = dimension.height / 2; double width = 0; for (int y = -1 * maxY; y <= 1 * maxY; y++) { if (y * notAnaglyph == 0) { width = dimension.width / 2; }else{ width = dimension.width / 4; } double size = Math.min(height, width); Orientation canvasOrientation = new Orientation(2); canvasOrientation.mirror(1); // y is upside-down on Java canvas canvasOrientation.sumRelative(0, dimension.width / 2 + notAnaglyph * swap * y * size); canvasOrientation.sumRelative(1, -dimension.height / 2); if (playfieldY[y + 1] == null) playfieldY[y + 1] = playfield; canvasOrientation.multiplyRelative(size / playfieldY[y + 1].getSize()); orientation = new Orientation(2); orientation.relative(canvasOrientation); orientation.sum(0, playfieldY[y + 1].copyPoint().getCoordinate(0)); if ((!stereo && y == 0) || (stereo && y != 0)) { scene[1][y + 1].draw(orientation, colorScheme[(1 - notAnaglyph) * y + 1], offGraphics); unit = size / playfield.getSize(); unit2 = size / playfieldSize; //offGraphics.drawString(score.toString(), (int)(dimension.width / 2 + notAnaglyph * swap * y * size + y), (int)(dimension.height/2 + size)); offGraphics.drawString(score.toString(), (int)(dimension.width / 2 + notAnaglyph * swap * y * size), (int)(dimension.height/2 + size)); } } graphics.drawImage(offImage, 0, 0, this); } }