house: house.html

<!DOCTYPE html>

<html>
<head>

<title>Comp 86 example: House</title>

<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.156.0/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@0.156.0/examples/jsm/"
    }
  }
</script>

<script type="module">

class SceneGraph extends THREE.Scene {
    constructor () {
	super ()

	// House, with appropriate origin
	let h = new House ()
	h.position.set (-8, -6, -10)
	this.add (h)
    }
}

/*
 * Foley & van Dam textbook house,
 * a subtree with 7 nodes, one each wall or surface
 */
class House extends THREE.Object3D { //*1 Our constants
    constructor () { //*1
	super ()

	/*
	 * Basic dimensions of the house, as constants
	 */
	House.X1HOUSE = 0; // Corner //*1
	House.Y1HOUSE = 0; //*1
	House.Z1HOUSE = 0; //*1
	House.X2HOUSE = 16; // Oppos corner of main body, not peak //*1
	House.Y2HOUSE = 10; //*1
	House.Z2HOUSE = -24; //*1
	House.XPEAKHOUSE = 8; // Top center of peak //*1
	House.YPEAKHOUSE = 16; //*1

	/*
	 * We'll keep our own master list of the 10 vertices of the house
	 */
	let v = []
	v[0] = new THREE.Vector3 (House.X1HOUSE, House.Y1HOUSE, House.Z1HOUSE);	// Coords of vertex 0 //*2 Our vertices
	v[1] = new THREE.Vector3 (House.X2HOUSE, House.Y1HOUSE, House.Z1HOUSE);	// Coords of vertex 1 //*2
	v[2] = new THREE.Vector3 (House.X2HOUSE, House.Y2HOUSE, House.Z1HOUSE); //*2
	v[3] = new THREE.Vector3 (House.XPEAKHOUSE, House.YPEAKHOUSE, House.Z1HOUSE); //*2
	v[4] = new THREE.Vector3 (House.X1HOUSE, House.Y2HOUSE, House.Z1HOUSE); //*2
	v[5] = new THREE.Vector3 (House.X1HOUSE, House.Y1HOUSE, House.Z2HOUSE); //*2
	v[6] = new THREE.Vector3 (House.X2HOUSE, House.Y1HOUSE, House.Z2HOUSE); //*2
	v[7] = new THREE.Vector3 (House.X2HOUSE, House.Y2HOUSE, House.Z2HOUSE); //*2
	v[8] = new THREE.Vector3 (House.XPEAKHOUSE, House.YPEAKHOUSE, House.Z2HOUSE); //*2
	v[9] = new THREE.Vector3 (House.X1HOUSE, House.Y2HOUSE, House.Z2HOUSE); //*2

	/*
	 * Front wall of house, made up of 3 triangles, all yellow
	 */
	let frontGeom = new THREE.BufferGeometry () //*3 Front wall
	frontGeom.setFromPoints ([ //*3
		// Vertices for the first triangle = front wall bottom right triangle,
		// chosen from the master list
		v[0], v[1], v[2], //*3

		// Vertices for the second triangle = front wall top left triangle
		v[2], v[4], v[0],  //*3

		// Front pediment
		v[2], v[3], v[4] //*3
	]);

	// For lighting calculcations
	frontGeom.computeVertexNormals(); //*5 Need normal vectors for lighting

	// Create the actual Mesh and add it to House
	this.add (new THREE.Mesh(frontGeom, //*3
		new THREE.MeshPhongMaterial ({ color: "yellow" }))) //*3

	/*
	 * Right wall of house, 2 triangles, green
	 */
	let rightGeom = new THREE.BufferGeometry ();
	rightGeom.setFromPoints ([
		v[1], v[6], v[7], //*4 Right wall
		v[7], v[2], v[1] //*4
	])
	rightGeom.computeVertexNormals();
	this.add (new THREE.Mesh (rightGeom, //*4
		new THREE.MeshPhongMaterial ({ color: "green" }))) //*4

    	/*
	 * Left wall, blue
	 */
	let leftGeom = new THREE.BufferGeometry ()
	leftGeom.setFromPoints ([
		v[0], v[4], v[9],
		v[9], v[5], v[0]
	])
	leftGeom.computeVertexNormals();
	this.add (new THREE.Mesh (leftGeom,
		new THREE.MeshPhongMaterial ({ color: "blue" })))

	/*
	 * Back wall of house, made up of 3 triangles, yellow
	 */
	let backGeom = new THREE.BufferGeometry ()
	backGeom.setFromPoints ([	    
	    v[5], v[9], v[7],
	    v[7], v[6], v[5],
	    v[7], v[9], v[8]
	])
	backGeom.computeVertexNormals();
	this.add (new THREE.Mesh (backGeom,
		new THREE.MeshPhongMaterial ({ color: "yellow" })))

    	/*
	 * Bottom (floor), red
	 */
	let bottomGeom = new THREE.BufferGeometry ()
	bottomGeom.setFromPoints ([
		v[0], v[5], v[6],
		v[6], v[1], v[0]
	])
	bottomGeom.computeVertexNormals();
	this.add (new THREE.Mesh (bottomGeom,
		new THREE.MeshPhongMaterial ({ color: "red" })))

    	/*
	 * Left roof, red
	 */
	let lroofGeom = new THREE.BufferGeometry ()
	lroofGeom.setFromPoints ([
		v[4], v[3], v[8],
		v[8], v[9], v[4]
	])
	lroofGeom.computeVertexNormals();
	this.add (new THREE.Mesh (lroofGeom,
		new THREE.MeshPhongMaterial ({ color: "red" })))

    	/*
	 * Right roof, magenta
	 */
	let rroofGeom = new THREE.BufferGeometry ()
	rroofGeom.setFromPoints ([
		v[2], v[7], v[8],
		v[8], v[3], v[2]
	])
	rroofGeom.computeVertexNormals();
	this.add (new THREE.Mesh (rroofGeom,
		new THREE.MeshPhongMaterial ({ color: "magenta" })))
    }
}

/*
 * An object to hold the lights, same as previous
 */
class Lights {
    constructor (scene) {
	// Uses typical lighting setup, like portrait or TV studio...

	// Main (key) light, directional, 
	// from 45 deg. user's right, above, bright white
	this.mainLight = new THREE.DirectionalLight ("white", 1 * 2*Math.PI)
	this.mainLight.position.set (1, 0.5, 1)
	scene.add (this.mainLight)

	// Fill light, directional, from 45deg. user's left,
	// white, half as bright
	this.fillLight = new THREE.DirectionalLight ("white", 0.5 * 2*Math.PI)
	this.fillLight.position.set (-1, 0, 1 )
	scene.add (this.fillLight)

	// Ambient light, white, still less bright
	this.ambientLight = new THREE.AmbientLight ("white", 0.05 * 2*Math.PI)
	scene.add (this.ambientLight);
    }
}

/*
 * The rest of this is the same boilerplate as previously
 */

import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

class Main {
	constructor () {
		// Set up window for 3D
		this.renderer = new THREE.WebGLRenderer( { antialias: true } );
		this.renderer.setClearColor( new THREE.Color ("lightgrey"))
		this.renderer.setSize( window.innerWidth, window.innerHeight );
		document.body.appendChild( this.renderer.domElement );

		// Create our scene
		this.scene = new SceneGraph();

		// Add our lights to the scene, we keep in a separate class 
		new Lights(this.scene); 

		// Create the camera
		this.camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
		this.camera.position.z = 5;

		// Create a camera contol
		new OrbitControls( this.camera, this.renderer.domElement );

		// Start animation loop
		this.animate()

		// In case window is resized
		// use () form of function definition cause need "this"
		// but don't need "event" arg
		window.onresize = () => this.onResize()
	}

	/*
	 * Render the scene
	 */
	render() {
		this.renderer.render( this.scene, this.camera );
	}

	/*
	 * Animation loop
	 */
	animate () {
		requestAnimationFrame (() => {this.animate()});
		this.render()
	}

	// In case window is resized
	onResize () {
		this.renderer.setSize( window.innerWidth, window.innerHeight );

		this.camera.aspect = window.innerWidth / window.innerHeight;
		this.camera.updateProjectionMatrix();

		this.render()
	}
}

window.onload = () => new Main ()

</script>
</head>
</html>
[download file]