//
```import java.awt.*;

// A VERY SIMPLE 3D RENDERER BUILT IN JAVA 1.0 - KEN PERLIN

public class SimpleRender extends BufferedApplet
{
int width = 0, height = 0;		// SIZE OF THE APPLET WINDOW
int nShapes;				// HOW MANY SHAPES IN THE SCENE
boolean debug = true;		// TO PRINT THINGS AT FRAME ONE
double theta = 0, phi = 0;		// VIEW ROTATION PARAMETERS

// 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

debug = false;
}

//----- THIS SECTION CONTAINS THE INPUT DATA FOR THE SCENE -----

// DIRECTION OF THE LIGHT SOURCE

double[] light = {1,1,1};

// FOR EACH SHAPE, ITS R,G,B COLOR

double[][] colors = {
{.3,1,.5},
{1,.5,.3},
};

// FOR EACH SHAPE, EACH FACE IS A LIST OF VERTEX INDICES

int[][][] faces = {
{{4,5,7,6}, {5,1,3,7}, {6,7,3,2}, {0,2,3,1}, {4,6,2,0}, {0,1,5,4}},
{{4,5,7,6}, {5,1,3,7}, {6,7,3,2}, {0,2,3,1}, {4,6,2,0}, {0,1,5,4}},
};

// FOR EACH SHAPE, LIST OF EACH VERTEX'S X,Y,Z COORDINATES

double[][][] vertices = {
{ {-1,-1,-1},{ 1,-1,-1},{-1, 1,-1},{ 1, 1,-1},
{-1,-1, 1},{ 1,-1, 1},{-1, 1, 1},{ 1, 1, 1} },
{ {-1,-1,-3},{ 1,-1,-3},{-1, 1,-3},{ 1, 1,-3},
{-1,-1,-1},{ 1,-1,-1},{-1, 1,-1},{ 1, 1,-1} },
};

//--------------------------------------------------------------

private double[][] camera;		// 4x4 MATRIX OF CAMERA TRANSFORM
private double[][][] matrices;	// FOR EACH SHAPE, A 4x4 MATRIX
private Color [][] color;		// FOR EACH FACE, A RENDERED COLOR
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 < nShapes ; i++)
identity(matrices[i]);		// THIS VERSION DOES NOTHING.
}

// TRANSFORM ALL VERTICES TO THEIR VIEWED POSITION

private void transform() {

// CREATE CAMERA MATRIX AND APPLY IT TO ALL SHAPE TRANSFORMATIONS

identity(camera);			    // BUILD CAMERA MATRIX FROM
rotateY(camera, theta);		    // phi,theta (DEFINED BY
rotateX(camera, phi);		    // USER'S MOUSE DRAGGING)

for (int i = 0 ; i < nShapes ; i++)   // ADD CAMERA TO ALL
preMultiply(matrices[i], camera);  // SHAPE TRANSFORMATIONS

// FOR ALL SHAPES, FOR ALL VERTICES, TRANSFORM AND PROJECT TO SCREEN

for (int i = 0 ; i < nShapes ; i++)
for (int j = 0 ; j < vertices[i].length ; j++) {
transformVertex(matrices[i], vertices[i][j], transformed[i][j]);
projectVertex  (transformed[i][j], projected  [i][j]);
}
}

// FOR ALL SHAPES, FOR ALL FACES, SHADE THAT FACE FOR THIS FRAME

for (int i = 0 ; i < nShapes ; i++)
for (int j = 0 ; j < faces[i].length ; j++)
}

// 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[nShapes];
for (int i = 0 ; i < z.length ; i++) {
order[i] = i;
z[i] = avgZ(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 avgZ(int id) {
double sum = 0;
for (int j = 0 ; j < transformed[id].length ; j++)
sum += transformed[id][j][2];
return sum / transformed[id].length;
}

// DRAW THE SCENE

private void draw(Graphics g) {

// DRAW A BLACK BACKGROUND

g.setColor(Color.black);
g.fillRect(0,0,width,height);

// FOR EACH SHAPE (IN BACK-TO-FRONT ORDER), DRAW EACH FACE

for (int i = 0 ; i < nShapes ; i++) {
int I = order[i];
for (int j = 0 ; j < faces[I].length ; j++) {
g.setColor(color[I][j]);
drawFace(g, faces[I][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] = 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];
normalize(u);
normalize(v);
cross(u,v,w);

// MAKE FACE BRIGHTER THE MORE IT FACES THE LIGHT SOURCE DIRECTION

double s = 100 * (dot(w,light) + 1);
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);
}

// 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).

private void initialize() {
width = bounds().width;
height = bounds().height;
nShapes = faces.length;

camera = new double[4][4];
matrices = new double[nShapes][4][4];
for (int i = 0 ; i < nShapes ; i++)
identity(matrices[i]);

color = new Color[nShapes][];
for (int i = 0 ; i < nShapes ; i++)
color[i] = new Color[faces[i].length];

transformed = new double[nShapes][][];
for (int i = 0 ; i < nShapes ; i++)
transformed[i] = new double[vertices[i].length][3];

projected = new int[nShapes][][];
for (int i = 0 ; i < nShapes ; i++)
projected[i] = new int[vertices[i].length][2];

order = new int[nShapes];
}

//----- VECTOR MATH -----

private void normalize(double[] v) {
double s = norm(v);
for (int i = 0 ; i < v.length ; i++)
v[i] /= s;
}

private double norm(double[] v) {
return Math.sqrt(dot(v,v));
}

private double dot(double[] a, double[] b) {
double sum = 0;
for (int i = 0 ; i < b.length ; i++)
sum += a[i] * b[i];
return sum;
}

private void cross(double[] a, double[] b, double[] c) {
c[0] = b[1] * a[2]) - b[2] * a[1];
c[1] = b[2] * a[0]) - b[0] * a[2];
c[2] = b[0] * a[1]) - b[1] * a[0];
}

//----- ROUTINES TO DO BASIC MATRIX MATH -----

private void identity(double[][] matrix) {
for (int i = 0 ; i < 4 ; i++)
for (int j = 0 ; j < 4 ; j++)
matrix[i][j] = (i == j ? 1 : 0);
}

private double[][] tmp = new double[4][4];

private void copy(double[][] src, double[][] dst) {
for (int i = 0 ; i < 4 ; i++)
for (int j = 0 ; j < 4 ; j++)
dst[i][j] = src[i][j];
}

private void preMultiply(double[][] dst, double[][] b) {
copy(dst, tmp);
for (int i = 0 ; i < 4 ; i++)
for (int j = 0 ; j < 4 ; j++) {
dst[i][j] = 0;
for (int k = 0 ; k < 4 ; k++)
dst[i][j] += tmp[i][k] * b[k][j];
}
}

private void postMultiply(double[][] dst, double[][] b) {
copy(dst, tmp);
for (int i = 0 ; i < 4 ; i++)
for (int j = 0 ; j < 4 ; j++) {
dst[i][j] = 0;
for (int k = 0 ; k < 4 ; k++)
dst[i][j] += b[i][k] * tmp[k][j];
}
}

//----- ROUTINES TO ROTATE AND TRANSLATE MATRICES -----

private double[][] mat = new double[4][4];

private void translate(double[][] m, double x, double y, double z) {
makeTranslationMatrix(mat, x,y,z);
postMultiply(m, mat);
}
private void rotateX(double[][] m, double theta) {
makeRotationMatrix(mat, 1,2, theta);
postMultiply(m, mat);
}
private void rotateY(double[][] m, double theta) {
makeRotationMatrix(mat, 2,0, theta);
postMultiply(m, mat);
}
private void rotateZ(double[][] m, double theta) {
makeRotationMatrix(mat, 0,1, theta);
postMultiply(m, mat);
}
private void scale(double[][] m, double x, double y, double z) {
makeScaleMatrix(mat, x,y,z);
postMultiply(m, mat);
}

//----- ROUTINES TO GENERATE TRANSFORMATION MATRICES -----

private void makeTranslationMatrix(double[][] m, double x, double y, double z) {
identity(m);
m[0][3] = x;
m[1][3] = y;
m[2][3] = z;
}
private void makeRotationMatrix(double[][] m, int i, int j, double theta) {
identity(m);
m[i][i] = m[j][j] = Math.cos(theta);
m[i][j] = -Math.sin(theta);
m[j][i] = -m[i][j];
}
private void makeScaleMatrix(double[][] m, double x, double y, double z) {
identity(m);
m[0][0] *= x;
m[1][1] *= y;
m[2][2] *= z;
}

//----- MOUSE HANDLERS TO LET THE USER ROTATE THE VIEW -----

int prevX, prevY;

// ON MOUSE DOWN, RECORD THE MOUSE POSITION

public boolean mouseDown(Event e, int x, int y) {
prevX = x;
prevY = y;
return true;
}

// USE CHANGE IN x,y TO MODIFY CAMERA theta,phi ROTATIONS

public boolean mouseDrag(Event e, int x, int y) {
theta += 5. / width * (x - prevX);
phi   += 5. / width * (y - prevY);
prevX = x;
prevY = y;
return true;
}
}

```