/*
$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);
}
}