//
import java.awt.*;
// A VERY SIMPLE 3D RENDERER BUILT IN JAVA 1.0 - KEN PERLIN
public class SimpleRender extends BufferedApplet
{
//----- THIS SECTION CONTAINS THE INPUT DATA FOR THE SCENE -----
public Color bgColor = Color.black;
public double[] light = {1,1,1};
public Shape[] shape = {new Shape()};
public double[][] colors = { {1,1,1}, };
public double[][][] transformations = { {{0,0,0}, {0,0,0}, {1,1,1}}, };
public boolean outline = false;
// THE RENDER ROUTINE IS CALLED BY THE APPLET ONCE PER FRAME
public void render(Graphics g) {
if (width == 0)
initialize(); // FIRST IME ONLY - INITIALIZE
animate(); // MOVE THE SHAPES MATRICES
transform(); // APPLY MATRICES TO ALL VERTICES
shade(); // LIGHT ALL POLYGON FACES
sort(); // SORT SHAPES FROM BACK TO FRONT
draw(g); // DRAW EVERYTHING TO AN IMAGE
renderWidgets(g);
debug = false;
}
//--------------------------------------------------------------
int width = 0, height = 0; // SIZE OF THE APPLET WINDOW
boolean debug = true; // TO PRINT THINGS AT FRAME ONE
double theta = 0, phi = 0; // VIEW ROTATION PARAMETERS
private double[][] camera; // 4x4 MATRIX OF CAMERA TRANSFORM
private double[][][] matrices; // FOR EACH SHAPE, A 4x4 MATRIX
private Color [][] rgb; // FOR EACH FACE, A RENDERED RGB
private double[][][] transformed; // TRANSFORM EACH VERTEX
private int[][][] projected; // PROJECT EACH TRANSFORMED VERTEX
private int[] order; // BACK-TO-FRONT SHAPE ORDERING
/// DEFINE THE ANIMATION MATRIX FOR THIS FRAME FOR AL SHAPES
private void animate() {
for (int i = 0 ; i < shape.length ; i++) {
shape[i].setColor(colors[i]);
shape[i].setTranslation(transformations[i][0]);
shape[i].setRotation(transformations[i][1]);
shape[i].setScale(transformations[i][2]);
}
}
// TRANSFORM ALL VERTICES TO THEIR VIEWED POSITION
private void transform() {
double[][] matrix = new double[4][4];
// CREATE CAMERA MATRIX AND APPLY IT TO ALL SHAPE TRANSFORMATIONS
Matrix.identity(camera); // BUILD CAMERA MATRIX FROM
Matrix.rotateX(camera, phi); // phi,theta (DEFINED BY
Matrix.rotateY(camera, theta); // USER'S MOUSE DRAGGING)
for (int i = 0 ; i < shape.length ; i++) { // ADD CAMERA TO ALL
shape[i].getMatrix(matrix);
Matrix.postMultiply(matrix, camera); // SHAPE TRANSFORMATIONS
// FOR ALL VERTICES, TRANSFORM AND PROJECT TO SCREEN
for (int j = 0 ; j < shape[i].vertices.length ; j++) {
transformVertex(matrix, shape[i].vertices[j], transformed[i][j]);
projectVertex (transformed[i][j], projected[i][j]);
}
}
}
// FOR ALL SHAPES, FOR ALL FACES, SHADE THAT FACE FOR THIS FRAME
private void shade() {
double[] color = new double[3];
for (int i = 0 ; i < shape.length ; i++)
for (int j = 0 ; j < shape[i].face.length ; j++) {
shape[i].getColor(color);
rgb[i][j] = shadeFace(color, shape[i].face[j], transformed[i]);
}
}
// FIGURE OUT THE BACK-TO-FRONT DRAWING ORDER FOR SHAPES.
private void sort() {
// FOR ALL SHAPES: COMPUTE ITS LARGEST (IE: NEAREST TO CAMERA) Z
double[] z = new double[shape.length];
for (int i = 0 ; i < z.length ; i++) {
order[i] = i;
z[i] = distSqr(i);
}
// SORT THE SHAPE DISPLAY ORDER SO NEAREST SHAPES WILL RENDER LAST
for (int i = 0 ; i < z.length-1 ; i++)
for (int j = i+1 ; j < z.length ; j++)
if (z[order[i]] > z[order[j]]) {
int tmp = order[i];
order[i] = order[j];
order[j] = tmp;
}
}
// LARGEST (IE: NEAREST TO CAMERA) Z OF SHAPE'S TRANSFORMED VERTICES
private double distSqr(int id) {
double[] sum = {0,0,0};
for (int j = 0 ; j < transformed[id].length ; j++)
for (int k = 0 ; k < 3 ; k++)
sum[k] += transformed[id][j][k];
double x = sum[0] / transformed[id].length;
double y = sum[1] / transformed[id].length;
double z = sum[2] / transformed[id].length - 10;
return -(x*x + y*y + z*z);
}
// DRAW THE SCENE
private void draw(Graphics g) {
// DRAW A BLACK BACKGROUND
g.setColor(bgColor);
g.fillRect(0,0,width,height);
// FOR EACH SHAPE (IN BACK-TO-FRONT ORDER), DRAW EACH FACE
for (int i = 0 ; i < shape.length ; i++) {
int I = order[i];
for (int j = 0 ; j < shape[I].face.length ; j++) {
g.setColor(rgb[I][j]);
drawFace(g, shape[I].face[j], projected[I]);
}
}
}
// TRANSFORM A VERTEX ACCORDING TO IT'S SHAPE'S MATRIX
private void transformVertex(double[][] matrix, double[] src, double[] dst) {
for (int i = 0 ; i < 3 ; i++)
dst[i] = Vec.dot(matrix[i], src) + matrix[i][3];
}
// PROJECT A TRANSFORMED VERTEX ONTO A PIXEL, WITH PERSPECTIVE.
private void projectVertex(double[] src, int[] dst) {
dst[0] = (int) ( width /2 + width * src[0] / (10 - src[2]) );
dst[1] = (int) ( height/2 - height * src[1] / (10 - src[2]) );
}
// COMPUTE FINAL DISPLAYED COLOR OF ONE POLYGON FACE
private Color shadeFace(double[] color, int[] face, double[][] verts) {
int R = 0, G = 0, B = 0;
// USE THREE SUCCESSIVE TRANSFORMED VERTICES a,b,c
double[] a = verts[face[0]];
double[] b = verts[face[1]];
double[] c = verts[face[2]];
// COMPUTE EDGE VECTORS u = b-a AND v = c-b
double[] u = {b[0]-a[0], b[1]-a[1], b[2]-a[2]};
double[] v = {c[0]-b[0], c[1]-b[1], c[2]-b[2]};
// TAKE CROSS PRODUCT OF NORMALIZED u,w TO GET FACE NORMAL w
double[] w = new double[3];
Vec.normalize(u);
Vec.normalize(v);
Vec.cross(u,v,w);
// MAKE FACE BRIGHTER THE MORE IT FACES THE LIGHT SOURCE DIRECTION
double s = 100 * (1 - Vec.dot(w,light));
R = (int) (color[0] * s);
G = (int) (color[1] * s);
B = (int) (color[2] * s);
// MULTIPLY BY THE SHAPE'S OVERALL COLOR.
R = Math.max(0, Math.min(255, R));
G = Math.max(0, Math.min(255, G));
B = Math.max(0, Math.min(255, B));
return new Color(R,G,B);
}
// DRAW ONE FACE
private void drawFace(Graphics g, int[] face, int[][] verts) {
// ARRANGE THE PROJECTED X AND Y VERTICES INTO THEIR ORDER IN THE FACE.
int[] X = new int[face.length];
int[] Y = new int[face.length];
for (int i = 0 ; i < face.length ; i++) {
X[i] = verts[face[i]][0];
Y[i] = verts[face[i]][1];
}
// IF FACE IS TOWARD CAMERA (IE: PROJECTED AREA IS POSITIVE) THEN DRAW IT.
if (area(X,Y) > 0) {
g.fillPolygon(X, Y, face.length);
if (outline) {
g.setColor(Color.white);
g.drawPolygon(X, Y, face.length);
}
}
}
// COMPUTE THE AREA OF A 2D POLYGON
private int area(int[] X, int[] Y) {
int sum = 0;
for (int i = 0 ; i < X.length ; i++) {
int j = (i + 1) % X.length;
sum += (X[j] - X[i]) * (Y[i] + Y[j]);
}
return sum / 2;
}
// INITIALIZE USEFUL PARAMETERS AND ALL ARRAYS (ONLY CALLED ONCE).
public void initialize() {
width = bounds().width;
height = bounds().height;
camera = new double[4][4];
matrices = new double[shape.length][4][4];
for (int i = 0 ; i < shape.length ; i++)
Matrix.identity(matrices[i]);
rgb = new Color[shape.length][];
for (int i = 0 ; i < shape.length ; i++)
rgb[i] = new Color[shape[i].face.length];
transformed = new double[shape.length][][];
for (int i = 0 ; i < shape.length ; i++)
transformed[i] = new double[shape[i].vertices.length][3];
projected = new int[shape.length][][];
for (int i = 0 ; i < shape.length ; i++)
projected[i] = new int[shape[i].vertices.length][2];
order = new int[shape.length];
update();
}
private int prevX = 0, prevY = 0;
// ON MOUSE DOWN, RECORD THE MOUSE POSITION
public boolean mouseDown(Event e, int x, int y) {
if (!widgetsDown(x,y)) {
prevX = x;
prevY = y;
}
return true;
}
public void update() { // IF SLIDERS OR BUTTONS CHANGE
}
// USE CHANGE IN x,y TO MODIFY CAMERA theta,phi ROTATIONS
public boolean mouseDrag(Event e, int x, int y) {
if (widgetsDrag(x,y))
update();
else {
theta += 5. / width * (x - prevX);
phi += 5. / width * (y - prevY);
prevX = x;
prevY = y;
}
return true;
}
public boolean mouseUp(Event e, int x, int y) {
if (widgetsUp(x, y))
update();
return true;
}
//----------------- HANDLE SLIDERS
public Slider[] slider = new Slider[100];
public Button[] button = new Button[100];
public int nSliders = 0, nButtons = 0;
void addButton(String[] labels) { addButton(labels,100,15*nButtons); }
void addButton(String[] labels, int x, int y) {
Button b = new Button(x, y, 50, 15);
button[nButtons] = b;
b.labels = labels;
nButtons++;
}
void addSlider(String label) { addSlider(label,0,15*nSliders); }
void addSlider(String label, int x, int y) {
Slider s = new Slider(x, y, 100, 15);
slider[nSliders] = s;
s.label = label;
s.setValue(0.5);
nSliders++;
}
void renderWidgets(Graphics g) {
for (int i = 0 ; i < nSliders ; i++)
slider[i].render(g);
for (int i = 0 ; i < nButtons ; i++)
button[i].render(g);
}
boolean widgetsDown(int x, int y) {
for (int i = 0 ; i < nSliders ; i++)
if (slider[i].down(x,y))
return true;
for (int i = 0 ; i < nButtons ; i++)
if (button[i].down(x,y))
return true;
return false;
}
boolean widgetsDrag(int x, int y) {
for (int i = 0 ; i < nSliders ; i++)
if (slider[i].drag(x,y))
return true;
for (int i = 0 ; i < nButtons ; i++)
if (button[i].drag(x,y))
return true;
return false;
}
boolean widgetsUp(int x, int y) {
for (int i = 0 ; i < nSliders ; i++)
if (slider[i].up(x,y))
return true;
for (int i = 0 ; i < nButtons ; i++)
if (button[i].up(x,y))
return true;
return false;
}
}