import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class PoolTest extends Applet implements Runnable {
private static final int TEX_SIZE = 128;
private static final int BALL_SIZE = 64;
private static final float TABLE_WIDTH = 640 - BALL_SIZE;
private static final float TABLE_HEIGHT = 480 - BALL_SIZE;
private Font font = new Font(Font.SANS_SERIF, Font.BOLD, 18);
private List<Ball> balls = new ArrayList<Ball>();
private BufferedImage tenBall = new BufferedImage(TEX_SIZE*2,TEX_SIZE*2,BufferedImage.TYPE_INT_ARGB);
private BufferedImage backBuffer = new BufferedImage(800,600, BufferedImage.TYPE_INT_ARGB);
private BufferedImage lightMap = new BufferedImage(BALL_SIZE,BALL_SIZE, BufferedImage.TYPE_INT_ARGB);
private class Ball {
BufferedImage img;
BufferedImage tex;
float[] rot;
float x,y;
float x1,y1;
public Ball(BufferedImage tex, int posX, int posY){
this.tex = tex;
rot = new float[]{0,0,0,1};
img = new BufferedImage(BALL_SIZE,BALL_SIZE,BufferedImage.TYPE_INT_ARGB);
x = posX;
y = posY;
x1 = posX;
y1 = posX;
}
public void setVel(float dx, float dy){
x1 = x - dx;
y1 = y - dy;
}
}
public PoolTest(){
// Draw the 10 ball texture.
Graphics2D g = (Graphics2D)tenBall.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE.darker());
g.fillRect(0,0,TEX_SIZE,TEX_SIZE);
g.setColor(Color.WHITE);
g.fillRect(0, 0, TEX_SIZE, TEX_SIZE/4);
g.fillRect(0, 3*TEX_SIZE/4,TEX_SIZE,TEX_SIZE/4);
g.translate(TEX_SIZE/2,TEX_SIZE/2);
g.scale(0.5, 1.0);
int diameter = TEX_SIZE/4;
g.fillOval(-diameter/2,-diameter/2,diameter,diameter);
g.setColor(Color.BLACK);
g.setFont(font);
int w = g.getFontMetrics().stringWidth("10");
int h = g.getFontMetrics().getAscent();
g.drawString("10",-w/2,h/3);
g.dispose();
float lightX = -0.4f;
float lightY = -0.4f;
// Draw the light map.
for(int ix = 0; ix < BALL_SIZE; ix++){
for(int iy = 0; iy < BALL_SIZE; iy++){
float x = (2*ix - BALL_SIZE)/(float)BALL_SIZE;
float y = (2*iy - BALL_SIZE)/(float)BALL_SIZE;
if(x*x + y*y < 1.0f){
float distLight = (x-lightX)*(x-lightX) + (y-lightY)*(y-lightY);
int alpha = 0;
int color = 0;
if (distLight < 0.2){
alpha = (int)((0.2f - distLight)*255);
color = 0xFFFFFF;
} else if(distLight > 0.5){
alpha = (int)((distLight - 0.5)*128);
}
lightMap.setRGB(ix,iy,color | (alpha >> 24));
}
}
}
try {
BufferedImage white = ImageIO.read(new URL("http://www.angryoctopus.co.nz/java4k/white_ball.png").openStream());
Ball whiteBall = new Ball(white,50,50);
whiteBall.setVel(-1f, -0.5f);
balls.add(whiteBall);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Ball b1 = new Ball(tenBall,100,100);
b1.setVel(3.0f, 2.0f);
balls.add(b1);
for(Ball ball : balls){
updateImage(ball);
}
}
public void start() {
new Thread(this).start();
}
public void run() {
while(true){
long delta = System.nanoTime();
for(Ball ball : balls){
updateBall(ball);
}
repaint();
delta -= System.nanoTime();
delta /= 1000000;
if (delta < 20){
try {
Thread.sleep(20-delta);
} catch (InterruptedException e) {}
}
}
}
public float clamp(float v, float min, float max){
if (v < min){
return min;
} else if (v > max){
return max;
} else {
return v;
}
}
public void updateBall(Ball ball){
float dx = ball.x - ball.x1;
float dy = ball.y - ball.y1;
float len = (float)Math.sqrt(dx*dx + dy*dy);
if (len > 0.01f){
rotate(ball.rot, dy/len, -dx/len,len*0.05f);
normalize(ball.rot);
updateImage(ball);
}
ball.x1 = ball.x;
ball.y1 = ball.y;
ball.x = clamp(ball.x,0,TABLE_WIDTH);
ball.y = clamp(ball.y,0,TABLE_HEIGHT);
ball.x += dx;
ball.y += dy;
}
public void paint(Graphics g){
Graphics2D bg = (Graphics2D)backBuffer.getGraphics();
bg.setColor(new Color(34,85,0));
bg.fillRect(0,0,getWidth(),getHeight());
bg.setColor(new Color(0,0,0,64));
bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(Ball ball : balls){
int x = (int)ball.x;
int y = (int)ball.y;
bg.fillOval(x + BALL_SIZE/4,y + BALL_SIZE/4,BALL_SIZE,BALL_SIZE);
}
for(Ball ball : balls){
int x = (int)ball.x;
int y = (int)ball.y;
bg.translate(x,y);
bg.drawImage(ball.img,0,0,null);
bg.drawImage(lightMap,0,0,null);
bg.translate(-x,-y);
}
bg.dispose();
g.drawImage(backBuffer,0,0,null);
}
public void update(Graphics g){
paint(g);
}
public void rotate(float[] q, float x, float y, float angle){
float n = (float)Math.sqrt(x*x + y*y);
float s = (float)Math.sin(0.5*angle)/n;
float q2x = x*s;
float q2y = y*s;
float q2w = (float)Math.cos(0.5*angle);
float dx, dy, dz, dw;
dx = q[0] * q2w + q[3] * q2x - q[2] * q2y;
dy = q[1] * q2w + q[3] * q2y + q[2] * q2x;
dz = q[2] * q2w + q[0] * q2y - q[1] * q2x;
dw = q[3] * q2w - q[0] * q2x - q[1] * q2y;
q[0] = dx;
q[1] = dy;
q[2] = dz;
q[3] = dw;
}
public void rotate(float[] q, float[] v){
float vx, vy, vz, vw;
vx = (q[3] * v[0] + q[1] * v[2] - q[2] * v[1]);
vy = (q[3] * v[1] + q[2] * v[0] - q[0] * v[2]);
vz = (q[3] * v[2] + q[0] * v[1] - q[1] * v[0]);
vw = (-q[0] * v[0] - q[1] * v[1] - q[2] * v[2]);
v[0] = vx * q[3] - vw * q[0] - vy * q[2] + vz * q[1];
v[1] = vy * q[3] - vw * q[1] - vz * q[0] + vx * q[2];
v[2] = vz * q[3] - vw * q[2] - vx * q[1] + vy * q[0];
}
public void normalize(float[] q){
float len = (float)Math.sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
q[0] /= len;
q[1] /= len;
q[2] /= len;
q[3] /= len;
}
public void updateImage(Ball ball){
float[] v = new float[3];
for(int ix = 0; ix < BALL_SIZE; ix++){
for(int iy = 0; iy < BALL_SIZE; iy++){
float x = (2*ix - BALL_SIZE)/(float)BALL_SIZE;
float y = (2*iy - BALL_SIZE)/(float)BALL_SIZE;
float z2 = (x*x + y*y);
if (z2 < 1.0f){
float z = (float)Math.sqrt(1.0f - z2);
v[0] = x;
v[1] = y;
v[2] = z;
int alpha = (int)(z*2.0f*255);
if (alpha > 255) alpha = 255;
alpha = (alpha << 24);
rotate(ball.rot, v);
float theta = (float)Math.acos(v[2]);
float phi = (float)(Math.atan2(v[1], v[0]) + Math.PI);
int ty = (int)((theta/Math.PI)*TEX_SIZE);
int tx = TEX_SIZE - (int)((phi/(Math.PI*2))*TEX_SIZE);
tx &= TEX_SIZE-1;
ty &= TEX_SIZE-1;
ball.img.setRGB(ix,iy,(ball.tex.getRGB(tx, ty) & 0xFFFFFF) | alpha);
} else {
ball.img.setRGB(ix,iy,0);
}
}
}
}
}