Lecture Notes Nine: Into the Third Dimension

 Motto: Never open a spaceman's helmet on an uncharted planet.

Here's `cubes.html`:

```<html>

<title>Some cubes</title>

<body>

<applet code="Cube.class"
width=400
height=400>
</applet>

</body>

</html>```
The approach now is top-down since we want

1. to see (to believe) and then
2. to understand (in this order)

So, here's `Cube.java`:

```import java.awt.*;
import java.net.*;
import java.io.*;

/*************************************************************
* A rotating cubes applet. See Chapter 11 of the BAJGP.
*
* Putting the classes to work with a quick and dirty applet.
*************************************************************/

public class Cube extends fNoFlickerApplet implements Runnable {

fGenericCamera camera;
fPoint3d camPos;
fAngle3d camAngle;

fPolyhedron cube;
fPolyhedronInstance cubeInstance[];
fPoint3d pos[];
fAngle3d agl;

public void init() {

//-- create a camera
camera=new fGenericCamera(400,400,Math.PI/2);
camPos=new fPoint3d(0,0,5);
camAngle=new fAngle3d();

//-- load a model from the file cube.f3d
try{
InputStream is=new URL(getCodeBase(),"cube.f3d").openStream();
cube=new fConvexPolyhedron(is);
} catch(Exception e) {
e.printStackTrace();
}

//-- create 9 instances of the cube
cubeInstance=new fPolyhedronInstance[9];
for(int n=0; n<9; n++) {
cubeInstance[n]=new fPolyhedronInstance(cube);
}

//-- create the positions and angle
pos=new fPoint3d[9];
int n=0;
for(int y=-5; y<=5; y+=5){
for(int x=-5; x<=5; x+=5){
pos[n]=new fPoint3d(x,y,0);
n++;
}
}
agl=new fAngle3d();

//-- start the thread
}

public void run() {
while(true) {

//-- sleep 1/10 of a second
try {
} catch ( InterruptedException e) {
// nothing
}

//-- update the angle of the models
agl.x+=Math.PI/20; agl.y+=Math.PI/30;

//-- update camera angle and position
camPos.z+=0.2; camAngle.z+=Math.PI/50;
camera.setOrientation(camPos,camAngle);

//-- request a repaint
repaint();
}
}

public void start() {
}
}

public void stop() {
}
}

public void paint(Graphics g) {

//-- clear screen
g.clearRect(0,0,size().width,size().height);

//-- paint the models
for(int n=0; n<9; n++){
cubeInstance[n].setOrientation(pos[n],agl);
cubeInstance[n].paint(g,camera);
}
}
}```
This requires the following (data) file, called `cube.f3d`:
```8
-1	1	1
-1	1	-1
1	1	-1
1	1	1
-1	-1	1
-1	-1	-1
1	-1	-1
1	-1	1
6
4	0 1 2 3		128 128 0
4	0 4 5 1 	128 0   128
4	1 5 6 2 	0   128 128
4	2 6 7 3 	128 0   0
4	0 3 7 4 	0   0   128
4	5 4 7 6 	0   128 0```
It also requires the following classes:
1. `fNoFlickerApplet`
2. `fGenericCamera`
3. `fPoint3d`
4. `fAngle3d`
5. `fPolyhedron`
6. `fPolyhedronInstance`
So we provide them.

```import java.awt.*;
import java.applet.*;

/**
* Represents a panel that does the painting offscreen
* which avoids flickering. Good for using in animations.
*/

class fNoFlickerApplet extends Applet {
private Image offScreenImage;
private Graphics offScreenGraphics;
private Dimension offScreenSize;

public final void update(Graphics theG){
Dimension d=size();
if((offScreenImage==null) ||
(d.width != offScreenSize.width) ||
(d.height != offScreenSize.height)) {

offScreenImage = createImage(d.width,d.height);
offScreenSize = d;
offScreenGraphics = offScreenImage.getGraphics();
offScreenGraphics.clearRect(0,0,offScreenSize.width,
offScreenSize.height);
}
paint(offScreenGraphics);
theG.drawImage(offScreenImage,0,0,null);
}
}```
Now the generic camera.

```/**
* A generic camera.
*/
public class fGenericCamera extends Object {
//-- a temporary buffer used for projection
protected static fArrayOf2dPoints our2dBuffer=
new fArrayOf2dPoints(new int[100],new int[100],100);
//-- a temporary buffer used for WCS to VCS transform
protected static fArrayOf3dPoints our3dBuffer=new fArrayOf3dPoints(100);
//-- the screen distance
protected double screendist;
//-- screen origo
protected int x0,y0;
//-- the viewangle
protected double myViewAngle;
//-- the matrix used for the WCS to VCS tranform
fMatrix3d myWCStoVCSmatrix;
//-- mark if the matrix is dirty
boolean matrixIsDirty;
//-- the position and angle of the camera in WCS
fPoint3d myPosition;
fAngle3d myAngle;

/**
* constructs a camera by specifing the widht, height and viewangle
*/
public fGenericCamera(int width,int height,double viewAngle){
myViewAngle=viewAngle;
//-- calculate the screen origo
x0=width>>1; y0=height>>1;
//-- calculate the screen distance
screendist=(double)x0/(Math.tan(viewAngle/2));
//-- construct the matrix
myWCStoVCSmatrix=new fMatrix3d();
//--
myPosition=new fPoint3d();
myAngle=new fAngle3d();
matrixIsDirty=true;
}
/**
* sets the position and angle of the camera.
*/
public void setOrientation(fPoint3d pos,fAngle3d agl){
if(myPosition.equals(pos)==false){
myPosition.set(pos);
matrixIsDirty=true;
}
if(myAngle.equals(agl)==false){
myAngle.set(agl);
matrixIsDirty=true;
}
}
/**
* projects an array of 3d points to the temporary 2d buffer
*/
public fArrayOf2dPoints project(fArrayOf3dPoints p3d){
//-- updates the matrix if it needed
updateMatrix();
//-- transform the WCS vertices to VCS storing the results
//-- in a buffer
myWCStoVCSmatrix.transform(p3d,our3dBuffer);
//-- project the VCS coordiantes to SCS storing the results
//-- in a buffer
for(int n=0;n<p3d.npoints;n++){
double z=our3dBuffer.z[n];
our2dBuffer.x[n]=-(int)(screendist*our3dBuffer.x[n]/z)+x0;
our2dBuffer.y[n]= (int)(screendist*our3dBuffer.y[n]/z)+y0;
}
//-- lend the buffer to the caller.
return our2dBuffer;
}
/**
* updates the matrix
*/
private void updateMatrix(){
if(matrixIsDirty==true){
//-- only remake the matrix if it is "dirty"
myWCStoVCSmatrix.makeWCStoVCStransform(myPosition,myAngle);
matrixIsDirty=false;
}
}
}```
Here's the three dimensional point:
```public class fPoint3d{

public double x;
public double y;
public double z;
public fPoint3d (double x0, double y0, double z0) {
x=x0; y=y0; z=z0;
}

public fPoint3d () {
x=y=z=0;
}

public fPoint3d (fPoint3d p1, fPoint3d p2) {
x=p2.x-p1.x; y=p2.y-p1.y; z=p2.z-p1.z;
}

public fPoint3d (fPoint3d p) {
x=p.x; y=p.y; z=p.z;
}

void vectorProduct (fPoint3d p1, fPoint3d p2) {
x=p1.y*p2.z-p1.z*p2.y;
y=p1.z*p2.x-p1.x*p2.z;
z=p1.x*p2.y-p1.y*p2.x;
}

void normalize (double length) {
double t=length/Math.sqrt(x*x+y*y+z*z);
x=t*x; y=t*y; z=t*z;
}

void plus (fPoint3d p) {
x+=p.x; y+=p.y; z+=p.z;
}

double dotProduct (fPoint3d p) {
return p.x*x+p.y*y+p.z*z;
}

boolean equals (fPoint3d p) {
return (p.x==x)&&(p.y==y)&&(p.z==z);
}

void set (fPoint3d p) {
x=p.x; y=p.y; z=p.z;
}

void times (double s) {
x*=s; y*=s; z*=s;
}

void negate () {
x=-x; y=-y; z=-z;
}

public String toString () {
return new String("("+x+", "+y+", "+z+")");
}

}```
Here are the polyhedron classes, starting with the abstract `Polyhedron` class:
```/*************************************
polyhedronclasses.java
*************************************/
import java.awt.*;
import java.io.*;
/**
* A polyhedron class that is made out of a list of vertices
* and a list of indexing polygons.
*/
abstract class fPolyhedron extends Object {
//-- the 3d coordiantes for the model
protected fArrayOf3dPoints myVertices;
//-- the polygons
protected fIndexingPolygon myPolygons[];
protected int nbrPolygons;
/**
* construct a polyhedron with the supplied data.
*/
protected fPolyhedron(fArrayOf3dPoints points,fIndexingPolygon polys[],int npolys){
myVertices=points;
myPolygons=polys;
nbrPolygons=npolys;
}
/**
* construct a polyhedron from a stream.
*/
public fPolyhedron(InputStream is) throws IOException {
fromString(is);
}
/**
* paint the polyhedron using the supplied 2d coordiantes.
*/
public abstract void paint(Graphics g,fArrayOf2dPoints point2d);
/**
* make a string representation of this polyhedron
*/
public String toString(){
String str=new String();
//-- make the array of 3d points into a stream
str=myVertices.toString();
//-- write to stream how many polygons there are
str=str+nbrPolygons+"\n";
//-- write all polygons to the stream
for(int n=0;n<nbrPolygons;n++){
str=str+myPolygons[n].toString();
}
return str;
}
/**
* read the polyhedron from a stream
*/
public void fromString(InputStream is) throws IOException {
//-- make a stream tokenizer
StreamTokenizer stream = new StreamTokenizer (is);
stream.commentChar('#');

//-- get the points
myVertices=new fArrayOf3dPoints(is);
myVertices.toString();
//-- get the # polygons
stream.nextToken(); nbrPolygons=(int)stream.nval;
//-- create the vector
myPolygons=new fIndexingPolygon[nbrPolygons];
//-- read each polygon
for(int n=0;n<nbrPolygons;n++){
myPolygons[n]=new fFilledPolygon(is);
}
}

public fArrayOf3dPoints getVertices(){
return myVertices;
}

public abstract fPolyhedron makeClone();

public void scalePoints(double fx,double fy,double fz){
for(int n=0;n<myVertices.npoints;n++){
myVertices.x[n]*=fx; myVertices.y[n]*=fy; myVertices.z[n]*=fz;
}
}
}

class fConvexPolyhedron extends fPolyhedron {
/**
* construct a polyhedron with the supplied data.
*/
public fConvexPolyhedron(fArrayOf3dPoints points,fIndexingPolygon polys[],int npolys){
super(points,polys,npolys);
}
/**
* construct a polyhedron from a stream.
*/
public fConvexPolyhedron(InputStream is) throws IOException {
super(is);
}
/**
* overrides fPolyhedron.paint(..)
* the polygons don't need to be sorted.
*/
public void paint(Graphics g,fArrayOf2dPoints point2d){
//-- the polygons don't have to be sorted
for(int n=0;n<nbrPolygons;n++){
myPolygons[n].paint(g,point2d.x,point2d.y);
}
}
/**
* Makes a clone of this polyhedron.
*/
public fPolyhedron makeClone(){
fIndexingPolygon polys[];
polys=new fIndexingPolygon[nbrPolygons];
for(int n=0;n<nbrPolygons;n++){
polys[n]=myPolygons[n].makeClone();
}
return new fConvexPolyhedron(myVertices.makeClone(),polys,nbrPolygons);
}
}```
Here's the polyhedron instance.

```/***************************
fpolyhedroninstance.java
****************************/
import java.awt.*;
/**
* Class that represents an instance of a polyhedron.
*/
public class fPolyhedronInstance extends Object {
//-- the transformed vertices
protected fArrayOf3dPoints transformedVertices;
//-- the matrix used for transformations
protected fMatrix3d myTransformMatrix;
//-- the polyhedron
protected fPolyhedron thePolyhedron;
//-- position in WCS
protected fPoint3d myPosition;
//-- the angle in WCS
protected fAngle3d myAngle;
//--
protected boolean positionIsDirty,angleIsDirty;

/**
* construct an instance of the supplied polyhedron.
*/
public fPolyhedronInstance(fPolyhedron poly){
//-- the polyhedron that this instance is using
thePolyhedron=poly;
//-- create the vertices to be used for storing transformations
try{
transformedVertices=(fArrayOf3dPoints)thePolyhedron.getVertices().makeClone();
} catch(Exception e){e.printStackTrace();}

myPosition=new fPoint3d();
myAngle=new fAngle3d();
myTransformMatrix=new fMatrix3d();
}

/**
* set the position and angle for this polyhedron instance.
*/
public void setOrientation(fPoint3d pos,fAngle3d agl){
if(myPosition.equals(pos)==false){
//-- if position has changed then mark the matrix
//-- as "dirty" meaning that the transformed points
//-- need to be updated.
myPosition.set(pos);
positionIsDirty=true;
}
if(myAngle.equals(agl)==false){
myAngle.set(agl);
angleIsDirty=true;
}
}

/**
* paint the polyhedron instance.
*/
public void paint(Graphics g,fGenericCamera camera){
if(positionIsDirty || angleIsDirty){
//-- position or angle has changed and the transformed
//-- vertices need to be updated.
myTransformMatrix.makeMCStoWCStransform(myPosition,myAngle);
//-- transform the polyhedron model coordinates to world coords.
myTransformMatrix.transform(thePolyhedron.getVertices(),transformedVertices);
//--
positionIsDirty=angleIsDirty=false;
}
//-- project the WCS to the screen with the supplied camera
//-- and then call the paint method of the polyhedron with
//-- the returned 2d array
thePolyhedron.paint(g,camera.project(transformedVertices));
}
} ```
A few other classes are needed as well:
1. some matrix classes
2. some helper classes
Here are the matrix classes:
```/********************
matrixclasses.java

*********************/

/**
* A generic 3d matrix class that implements the rotation
* about the principal axis, translation and scaling.
*/
class fGeneric3dMatrix extends Object {
double xx, xy, xz, xo;
double yx, yy, yz, yo;
double zx, zy, zz, zo;

/**
* Constructs the identity matrix.
*/
public fGeneric3dMatrix(){
makeIdentity();
}
/**
* Resets the matrix.
*/
public void makeIdentity(){
xx = 1; xy = 0; xz = 0; xo = 0;
yx = 0; yy = 1; yz = 0; yo = 0;
zx = 0; zy = 0; zz = 1; zo = 0;
}
/**
* "Smart" multiplies a rotation about Z-axis
*/
public void concatRz(double az){
double ct = Math.cos(az);
double st = Math.sin(az);

double Nyx = (yx * ct + xx * st);
double Nyy = (yy * ct + xy * st);
double Nyz = (yz * ct + xz * st);
double Nyo = (yo * ct + xo * st);

double Nxx = (xx * ct - yx * st);
double Nxy = (xy * ct - yy * st);
double Nxz = (xz * ct - yz * st);
double Nxo = (xo * ct - yo * st);

xx = Nxx; xy = Nxy; xz = Nxz; xo = Nxo;
yx = Nyx; yy = Nyy; yz = Nyz; yo = Nyo;
}
/**
* "Smart" multiplies a rotation about Y-axis
*/
public void concatRy(double ay){
double ct = Math.cos(ay);
double st = Math.sin(ay);

double Nxx = (xx * ct + zx * st);
double Nxy = (xy * ct + zy * st);
double Nxz = (xz * ct + zz * st);
double Nxo = (xo * ct + zo * st);

double Nzx = (zx * ct - xx * st);
double Nzy = (zy * ct - xy * st);
double Nzz = (zz * ct - xz * st);
double Nzo = (zo * ct - xo * st);

xx = Nxx; xy = Nxy; xz = Nxz; xo = Nxo;
zx = Nzx; zy = Nzy; zz = Nzz; zo = Nzo;
}
/**
* "Smart" multiplies a rotation about X-axis
*/
public void concatRx(double ax){
double ct = Math.cos(ax);
double st = Math.sin(ax);

double Nyx = (yx * ct + zx * st);
double Nyy = (yy * ct + zy * st);
double Nyz = (yz * ct + zz * st);
double Nyo = (yo * ct + zo * st);

double Nzx = (zx * ct - yx * st);
double Nzy = (zy * ct - yy * st);
double Nzz = (zz * ct - yz * st);
double Nzo = (zo * ct - yo * st);

yx = Nyx; yy = Nyy; yz = Nyz; yo = Nyo;
zx = Nzx; zy = Nzy; zz = Nzz; zo = Nzo;
}
/**
* "Smart" multiplies a translation
*/
public void concatT(double x,double y,double z){
xo+=x; yo+=y; zo+=z;
}
/**
* "Smart" multiplies scaling
*/
public void concatS(double sx,double sy,double sz){
xx *= sx; xy *= sx; xz *= sx; xo *= sx;
yx *= sy; yy *= sy; yz *= sy; yo *= sy;
zx *= sz; zy *= sz; zz *= sz; zo *= sz;
}
/**
* Multiplies the vector "ps" of 3d points and stores the result
* in "pd".
*/
public void transform(fArrayOf3dPoints ps,fArrayOf3dPoints pd){
for (int i=0; i<ps.npoints; i++) {
double x=ps.x[i]; double y=ps.y[i]; double z=ps.z[i];
pd.x[i] = x*xx + y*xy + z*xz + xo;
pd.y[i] = x*yx + y*yy + z*yz + yo;
pd.z[i] = x*zx + y*zy + z*zz + zo;
}
}
}
/**
* A 3d matrix that hides the making of the different
* transforms
*/
class fMatrix3d extends fGeneric3dMatrix {
/**
* construct the matrix
*/
public fMatrix3d(){
super();
}
/**
* let matrix contain the MCS to WCS transform
*/
public void makeMCStoWCStransform(fPoint3d pos,fAngle3d agl,fPoint3d scale){
makeIdentity();
concatS(scale.x,scale.y,scale.z);
concatRx(agl.x);
concatRy(agl.y);
concatRz(agl.z);
concatT(pos.x,pos.y,pos.z);
}
/**
* let matrix contain the MCS to WCS transform, without scaling
*/
public void makeMCStoWCStransform(fPoint3d pos,fAngle3d agl){
makeIdentity();
concatRx(agl.x);
concatRy(agl.y);
concatRz(agl.z);
concatT(pos.x,pos.y,pos.z);
}
/**
* let matrix contain the WCS to MCS transform
*/
public void makeWCStoVCStransform(fPoint3d pos,fAngle3d agl){
makeIdentity();
concatT(-pos.x,-pos.y,-pos.z);
concatRz(-agl.z);
concatRy(-agl.y);
concatRx(-agl.x);
}

public void makeLookAtPointTransform(fPoint3d p0,fPoint3d p1){
fPoint3d vecZaxis=new fPoint3d(p0,p1);
vecZaxis.normalize(1);
fPoint3d vecXaxis=new fPoint3d();
vecXaxis.vectorProduct(new fPoint3d(0,1,0),p1);
vecXaxis.normalize(1);

fPoint3d vecYaxis=new fPoint3d();
vecYaxis.vectorProduct(vecZaxis,vecXaxis);

xo=-p0.x; yo=-p0.y; zo=-p0.z;
xx=vecXaxis.x; xy=vecXaxis.y; xz=vecXaxis.z;
yx=vecYaxis.x; yy=vecYaxis.y; yz=vecYaxis.z;
zx=vecZaxis.x; zy=vecZaxis.y; zz=vecZaxis.z;
}

}

```
Here are the helper classes:
```/*********************
polygonclasses.java
**********************/

import java.awt.*;
import java.io.*;
/**
* Describes an abstract indexing polygon.
*/
abstract class fIndexingPolygon extends Object{
/**
* construct a polygon with the supplied indices
*/
protected fIndexingPolygon(int indices[],int n){
myIndices=indices;
nbrIndices=n;
}
/**
* construct a polygon from a stream
*/
protected fIndexingPolygon(InputStream is) throws IOException {
fromString(is);
}
/**
* paints a polygon. the 2d list of coordiantes must be supplied
*/
public abstract void paint(Graphics g,int x[],int y[]);
/**
* read a polygon from a stream
*/
public void fromString(InputStream is) throws IOException {
//-- make a stream tokenizer
StreamTokenizer stream = new StreamTokenizer (is);
stream.commentChar('#');

//-- get the # of indicies in this polygon
stream.nextToken(); nbrIndices=(int)stream.nval;
//-- allocate the vector
myIndices=new int[nbrIndices];
//-- read all indices
for(int i=0;i<nbrIndices;i++){
stream.nextToken(); myIndices[i]=(int)stream.nval;
}
}
/**
* make a string representation of a polygon
*/
public String toString(){
String str=new String();
str=str+nbrIndices;
for(int n=0;n<nbrIndices;n++){
str=str+" "+myIndices[n];
}
return str;
}
/**
* pokes out the 2d coordiantes and stores them into the
* scratch polygon.
*/
protected void copyIndexedPoints(int x[],int y[]){
for(int n=0;n<nbrIndices;n++){
int i=myIndices[n];
ourScratchPoly.xpoints[n]=x[i];
ourScratchPoly.ypoints[n]=y[i];
}
ourScratchPoly.npoints=nbrIndices;
}
/**
* determine the orientation of the scratch polygon.
* if the result is positive then it is CW.
*/
protected static int orientation() {
int p1x=ourScratchPoly.xpoints[1],p1y=ourScratchPoly.ypoints[1];
//-- vector from vertex #1 to vertex #2
int v1x=ourScratchPoly.xpoints[2]-p1x;
int v1y=ourScratchPoly.ypoints[2]-p1y;
//-- vector from vertex #1 to vertex #0
int v2x=ourScratchPoly.xpoints[0]-p1x;
int v2y=ourScratchPoly.ypoints[0]-p1y;
//-- return the determinant of the vectors
return v1x*v2y-v2x*v1y;
}
/**
* make a clone of this polygon
*/
public abstract fIndexingPolygon makeClone();
/**
* the "scratch" polygon that is used for painting
*/
protected static Polygon ourScratchPoly=new Polygon(new int[50],new int[50],50);
/**
* the indices that define this polygon
*/
protected int myIndices[];
/**
* number of indices in this polygon.
*/
protected int nbrIndices;
}

/**
* A solid color polygon.
*/
class fFilledPolygon extends fIndexingPolygon {
/**
* The color of this polygon.
*/
protected fColor myColor;
/**
* Create a polygon with the supplied data.
*/
public fFilledPolygon(int indices[],int n,fColor color){
super(indices,n);
myColor=color;
}
/**
* Create a polygon from a stream.
*/
public fFilledPolygon(InputStream is) throws IOException {
super(is);
}
/**
* paints the polygon if it is cw
*/
public void paint(Graphics g,int x[],int y[]){
//-- copy the indexed coordiantes from the 2d list to
copyIndexedPoints(x,y);
render(g);
}
/**
* The actual rendering.
*/
protected void render(Graphics g){
//-- check orientation
if(orientation()>0){
g.setColor(myColor.getColor());
g.fillPolygon(ourScratchPoly);
}
}

/**
* overrides fIndexingPolygon.toString()
* the color must also be written to the string.
*/
public String toString(){
//-- make the string for fIndexingPolygon
String str=super.toString();
//-- add the color and line break
str=str+" "+myColor.toString()+"\n";
return str;
}
/**
* overrides fIndexingPolygon.toString()
* the color must also be read from the stream.
*/
public void fromString(InputStream is) throws IOException {
super.fromString(is);
//-- read the color
myColor=new fColor(is);
}
/**
* Makes a clone of this polygon.
*/
public fIndexingPolygon makeClone(){
int i[];
System.arraycopy(myIndices,0,i=new int[nbrIndices],0,nbrIndices);
return new fFilledPolygon(i,nbrIndices,myColor.makeClone());
}
} ```
And a few more:
```public class fArrayOf2dPoints extends Object {
int x[],y[];
int npoints;

public fArrayOf2dPoints(int x0[],int y0[],int n){
x=x0; y=y0; npoints=n;
}
}
```
Then this one:
```import java.io.*;
/**
* A class that encapsulates and array of 3d points.
*/
public class fArrayOf3dPoints extends Object {
double x[],y[],z[];
int npoints;
/**
* Constructs an array of 3d points with the supplied vectors.
*/
fArrayOf3dPoints(double x0[],double y0[],double z0[],int n){
x=x0; y=y0; z=z0; npoints=n;
}
/**
* Constructs an empty array of 3d points with size "n"
*/
fArrayOf3dPoints(int n){
npoints=n;
x=new double[n]; y=new double[n]; z=new double[n];
}
/**
* construct an array of 3d points from a stream
*/
fArrayOf3dPoints(InputStream is) throws IOException{
fromString(is);
}
/**
* ovrrides the Object method
*/
public String toString(){
String str=new String();
//-- the number of vertices
str=" "+npoints+"\n";
//-- concat the coordinates to the string
for(int n=0;n<npoints;n++){
str=str+x[n]+" "+y[n]+" "+z[n]+"\n";
}
return str;
}
/**
* Returns a clone.
*/
fArrayOf3dPoints makeClone(){
double xnew[],ynew[],znew[];
System.arraycopy(x,0,xnew=new double[npoints],0,npoints);
System.arraycopy(y,0,ynew=new double[npoints],0,npoints);
System.arraycopy(z,0,znew=new double[npoints],0,npoints);
return new fArrayOf3dPoints(xnew,ynew,znew,npoints);
}
/**
* Reads an array from a stream
*/
void fromString(InputStream is) throws IOException {
//-- make a stream tokenizer
StreamTokenizer stream = new StreamTokenizer (is);
stream.commentChar('#');

//-- get the # points
stream.nextToken(); npoints=(int)stream.nval;

//-- create the vectors
x=new double[npoints];
y=new double[npoints];
z=new double[npoints];

//-- read the coordiantes
for(int n=0;n<npoints;n++){
stream.nextToken(); x[n]=(double)stream.nval;
stream.nextToken(); y[n]=(double)stream.nval;
stream.nextToken(); z[n]=(double)stream.nval;
}
}
}
```
Then this one:
```import java.awt.*;
import java.io.*;
/**
* Wraps the java.awt.Color class provided by Java
*/
public class fColor extends Object {
public int r,g,b;
protected Color myBaseColor;

/**
* construct a color with the RGB supplied.
*/
public fColor(int r0,int g0,int b0){
r=r0; g=g0; b=b0;
myBaseColor=new Color(r,g,b);
}
/**
* constructs a color from a stream
*/
public fColor(InputStream is) throws IOException{
fromString(is);
myBaseColor=new Color(r,g,b);
}
/**
* returns the base color
*/
public Color getColor(){
return myBaseColor;
}
/**
* read the color from a stream
*/
public void fromString(InputStream is) throws IOException{
//-- make a stream tokenizer
StreamTokenizer stream = new StreamTokenizer (is);
stream.commentChar('#');

//-- read the RGB triple
stream.nextToken(); r=(int)stream.nval;
stream.nextToken(); g=(int)stream.nval;
stream.nextToken(); b=(int)stream.nval;
}
/**
* make a string representation
*/
public String toString(){
return new String(" "+r+" "+g+" "+b+" ");
}
/**
* Makes a clone of this color.
*/
public fColor makeClone(){
return new fColor(r,g,b);
}
}
```
So we compile everything and here's the applet.

Once we see we are ready to understand it, and it's simple.

Last updated: Dec 26, 2001 by Adrian German for A348/A548