import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.Color;
public class Landscape extends Applet
{
// lod = Level Of Detail
// if step is 1 and lod is five then step becomes 32
// (binary shift 5 places) this is how
//the level of detail is organised.
private static final int lod = 5, steps = 1 << lod;
//was 0.5 how rough do we want the terrain .1 is
//very smooth but .5 is very very rough
private static final double roughness = 0.2;
//was 0.7, the exaggeration is
private static final double exaggeration = 0.2;
//how bright?
private static final double ambient = 0.2;
//for the render was .7
private static final double diffuse = 0.7;
private static final double strength = 8.0;
static final double hither = .1;
// PI=3.14159265 /6 = 0.523598775
//set the zoom on start up of applet
static final double fov = Math.PI / 6;
private Terrain terrain;
private int width, height;
private Triple[][] map;
private RGB[][] colors;
private double[][] shade;
private Triple[][] normals;
private Triple[][] eyeMap;
private XY[][] scrMap;
private int numTriangles = steps * steps * 2;
private Triangle[] triangles;
private Triple sun = new Triple (3.6, 1.5, 0.6);
private Triple loc = new Triple (0.4, 2.4, -1.6);
private Quaternion rot = Quaternion.newRotation (-.82, 1.0, 0.0, 0.0);
private Rasteriser rasteriser;
Font f = new Font("TimesRoman",Font.BOLD,12);
String name;
String message;
public void init ()
{
//pick the frame background colour, white is good :)
setBackground (Color.white);
//make a map
map = new Triple[steps + 1][steps + 1];
colors = new RGB[steps + 1][steps + 1];
shade = new double[steps + 1][steps + 1];
normals = new Triple[steps + 1][steps + 1];
eyeMap = new Triple[steps + 1][steps + 1];
scrMap = new XY[steps + 1][steps + 1];
triangles = new Triangle[numTriangles];
terrain = new FractalTerrain (lod, roughness);
// fill in vertices
for (int i = 0; i <= steps; ++ i) {
for (int j = 0; j <= steps; ++ j) {
double x = 1.0 * i / steps, z = 1.0 * j / steps;
double altitude = exaggeration * terrain.getAltitude (x, z);
map[i][j] = new Triple (x, altitude, z);
colors[i][j] = terrain.getColor (x, z);
shade[i][j] = 1.0;
normals[i][j] = new Triple (0.0, 0.0, 0.0);
}
}
// set up tessellation
int triangle = 0;
for (int i = 0; i < steps; ++ i) {
for (int j = 0; j < steps; ++ j) {
triangles[triangle ++] = new Triangle (i, j, i + 1, j, i, j + 1);
triangles[triangle ++] = new Triangle (i + 1, j, i + 1, j + 1, i, j + 1);
}
}
// compute triangle and vertex normals
for (int i = 0; i < numTriangles; ++ i) {
Triple v0 = map[triangles[i].i[0]][triangles[i].j[0]],
v1 = map[triangles[i].i[1]][triangles[i].j[1]],
v2 = map[triangles[i].i[2]][triangles[i].j[2]];
Triple normal = v0.subtract (v1).cross (v2.subtract (v1)).normalise ();
triangles[i].n = normal;
for (int j = 0; j < 3; ++ j) {
normals[triangles[i].i[j]][triangles[i].j[j]] =
normals[triangles[i].i[j]][triangles[i].j[j]].add (normal);
}
}
// compute vertex shadows
for (int i = 0; i <= steps; ++ i)
{
for (int j = 0; j <= steps; ++ j)
{
shade[i][j] = 1.0;
Triple vertex = map[i][j];
Triple ray = sun.subtract (vertex);
double distance = steps * Math.sqrt (ray.x * ray.x + ray.z * ray.z);
/* step along ray in horizontal units of grid width */
for (double place = 1.0; place < distance; place += 1.0)
{
Triple sample = vertex.add (ray.scale (place / distance));
double sx = sample.x, sy = sample.y, sz = sample.z;
if ((sx < 0.0) || (sx > 1.0) || (sz < 0.0) || (sz > 1.0))
break; /* stepped off terrain */
double ground = exaggeration * terrain.getAltitude (sx, sz);
if (ground >= sy)
{
shade[i][j] = 0.0;
break;
}
}
}
}
// compute triangle and vertex colors
for (int i = 0; i < numTriangles; ++ i)
{
RGB average = new RGB (0.0, 0.0, 0.0);
for (int j = 0; j < 3; ++ j)
{
int k = triangles[i].i[j], l = triangles[i].j[j];
Triple vertex = map[k][l];
RGB color = colors[k][l];
double shadow = shade[k][l];
Triple normal = normals[k][l].normalise ();
Triple light = vertex.subtract (sun);
double distance2 = light.length2 ();
double dot = light.normalise ().dot (normal);
double lighting = ambient;
if (dot < 0.0) // add diffuse lighting
lighting -= strength * diffuse * dot * shadow / distance2;
color = color.scale (lighting);
triangles[i].rgb[j] = color;
average = average.add (color);
}
triangles[i].color = new Color (average.scale (1.0 / 3.0).toRGB ());
}
width = getSize ().width / 2;
height = getSize ().height;
worldToEye ();
eyeToScreen ();
//check that rasterizer width and height are not empty if they
//are then pass in the width and height(NOTE: the width is half
//the applet width).
if (rasteriser == null)
{
rasteriser = new Rasteriser (width, height);
//create a canvas to draw the high quality picture to
Viewer viewer = new Viewer (rasteriser);
//make a flowlayout and put it on the right side of the applet
setLayout (new FlowLayout (FlowLayout.RIGHT, 0, 0));
add (viewer);
}
else
{
rasteriser = new Rasteriser (width + width, height);
//create a canvas to draw the high quality picture to
Viewer viewer = new Viewer (rasteriser);
//make a flowlayout and put it on the right side of the applet
setLayout (new FlowLayout (FlowLayout.LEFT, 0, 0));
add (viewer);
}
enableEvents (AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
requestFocus ();
this.name = getParameter("name");
if (this.name == null)
this.name = "Tony";
this.name = "Hello " + this.name + "!";
this.message = "paint called";
repaint ();
requestFocus ();
}
public void start()
{
requestFocus ();
}
protected void processMouseEvent (MouseEvent ev)
{
if (ev.getID () == MouseEvent.MOUSE_PRESSED)
requestFocus ();
super.processMouseEvent (ev);
}
protected void processKeyEvent (KeyEvent ev)
{
double r, x, y, z;
if (ev.getID () != KeyEvent.KEY_PRESSED)
{
super.processKeyEvent (ev);
return;
}
int key = ev.getKeyCode ();
// rotate but which way
if ((key == KeyEvent.VK_UP) || (key == KeyEvent.VK_DOWN) || (key == KeyEvent.VK_LEFT) || (key == KeyEvent.VK_RIGHT) || (key == KeyEvent.VK_COMMA) || (key == KeyEvent.VK_PERIOD))
{
r = ((key == KeyEvent.VK_DOWN) || (key == KeyEvent.VK_RIGHT) || (key == KeyEvent.VK_PERIOD)) ? - Math.PI / 64 : Math.PI / 64;
x = ((key == KeyEvent.VK_UP) || (key == KeyEvent.VK_DOWN)) ? 1.0 : 0.0;
y = ((key == KeyEvent.VK_LEFT) || (key == KeyEvent.VK_RIGHT)) ? 1.0 : 0.0;
z = ((key == KeyEvent.VK_COMMA) || (key == KeyEvent.VK_PERIOD)) ? 1.0 : 0.0;
Quaternion delta = Quaternion.newRotation (r, x, y, z);
// rot is rotation
rot = delta.multiply (rot);
worldToEye ();
eyeToScreen ();
repaint ();
}
//z = bigger x = smaller
else if ((key == KeyEvent.VK_Z) || (key == KeyEvent.VK_X))
{ //make bigger: move the map closer or smaller
z = (key == KeyEvent.VK_Z) ? 1.0 / 8: -1.0 / 8;
Triple delta = new Triple (0.0, 0.0, z);
Quaternion tmp = rot.inverse ();
Triple deltar = tmp.rotate (delta);
loc = loc.add (deltar);
worldToEye ();
eyeToScreen ();
repaint ();
// rasterize
}
else if (key == KeyEvent.VK_ENTER) //rasterise it
{
rasteriser.clear ();
enterKeyPressed();
rasteriser.update ();
//rasteriser.savePPM("c:\terrain\testoutput.txt");
}
else if (key == KeyEvent.VK_SPACE) // make a new landscape
{
initalSetup ();
//this is sort of a clipping affect as it
// only paints half the applet
repaint (0, 0, width, height);
}
super.processKeyEvent (ev);
}
private void worldToEye ()
{
for (int i = 0; i <= steps; ++ i)
{
for (int j = 0; j <= steps; ++ j)
{
Triple p = map[i][j];
Triple t = p.subtract (loc);
Triple r = rot.rotate (t);
eyeMap[i][j] = r;
}
}
}
private void eyeToScreen ()
{
double scale = width / 2 / Math.tan (fov / 2);
for (int i = 0; i <= steps; ++ i)
{
for (int j = 0; j <= steps; ++ j)
{
Triple p = eyeMap[i][j];
double x = p.x, y = p.y, z = p.z;
if (z >= hither)
{
double tmp = scale / z;
scrMap[i][j] = new XY (width / 2 + (int) (x * tmp), height / 2 - (int) (y * tmp));
}
else
{
scrMap[i][j] = null;
}
}
}
}
public void paint (Graphics g)
{
g.setFont(f);
g.setColor(Color.black);
g.drawString(this.name, 5, 50);
//draw the map :)
for (int i = 0; i < numTriangles; ++ i)
{
XY xy0 = scrMap[triangles[i].i[0]][triangles[i].j[0]],
xy1 = scrMap[triangles[i].i[1]][triangles[i].j[1]],
xy2 = scrMap[triangles[i].i[2]][triangles[i].j[2]];
double dot =- map[triangles[i].i[0]][triangles[i].j[0]].subtract (loc).normalise ().dot (triangles[i].n);
if ((dot > 0.0) && (xy0 != null) && (xy1 != null) && (xy2 != null))
{
int[] x = { xy0.x, xy1.x, xy2.x }, y = { xy0.y, xy1.y, xy2.y };
g.setColor (triangles[i].color);
g.fillPolygon (x, y, 3);
}
}
}
public Dimension getPreferredSize ()
{
//set size of the black area
//return new Dimension (512, 256);
return new Dimension (1000, 500);
}
private void enterKeyPressed()
{
for (int i = 0; i < numTriangles; ++ i)
{
XY xy0 = scrMap[triangles[i].i[0]][triangles[i].j[0]],
xy1 = scrMap[triangles[i].i[1]][triangles[i].j[1]],
xy2 = scrMap[triangles[i].i[2]][triangles[i].j[2]];
double dot = - map[triangles[i].i[0]][triangles[i].j[0]].subtract (loc).normalise ().dot (triangles[i].n);
dot = 1.0;
if ((dot > 0.0) && (xy0 != null) && (xy1 != null) && (xy2 != null))
{
double z0 = eyeMap[triangles[i].i[0]][triangles[i].j[0]].z,
z1 = eyeMap[triangles[i].i[1]][triangles[i].j[1]].z,
z2 = eyeMap[triangles[i].i[2]][triangles[i].j[2]].z;
Vertex v0 = new Vertex (xy0, z0, triangles[i].rgb[0]),
v1 = new Vertex (xy1, z1, triangles[i].rgb[1]),
v2 = new Vertex (xy2, z2, triangles[i].rgb[2]);
rasteriser.rasterise (v0, v1, v2);
}
}
}
public void initalSetup()
{
}
//start the program
public static void main (String[] args)
{
//create a frame to paint on
Frame frame = new Frame ("Landscape");
//make an instance of landscape
Landscape landscape = new Landscape ();
//add the landscape to the frame
frame.add (landscape, "Center");
frame.pack ();
// call init() in the landscape object
landscape.init ();
// make the frame visible
frame.setVisible (true);
}
}