robot1: robot1.html

<!DOCTYPE html>

<html>
<head>

<title>Comp 86 example: Robot, without animation</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()

	// First robot, position = WC origin to robot local origin
	let r = new Robot () //*1 Add two robots to scene graph
	r.position.set (0, -5, -10) //*1
	this.add (r) //*1

	// A second robot, different local origin
	r = new Robot () //*1
	r.position.set (-5, -5, -20) //*1
	r.rotation.set (0, -20 * Math.PI/180, 0) //*1
	this.add (r) //*1
    }
}

/*
 * We make a Robot out of other library objects
 */
class Robot extends THREE.Object3D {
    constructor () {
	super ()

	/*
         * Initialize some Materials we will be using
	 */
	let platform = new THREE.MeshPhongMaterial ({ color: 0x4c4c33 }) //*2 Some stock materials
	let pants = new THREE.MeshPhongMaterial ({ color: 0x0c0c4c }) //*2
	let shirt = new THREE.MeshPhongMaterial ({ color: 0xffcc80 }) //*2
	let skin = new THREE.MeshPhongMaterial ({ color: 0xffd9b3, shininess: 60 }) //*2

	/*
	 * The base
	 */
	// The bottom flat part of base
	let base = new THREE.Mesh ( //*3 Base
		new THREE.BoxGeometry (1.5, 1, 1.25), platform) //*3
	// (transform = local offset for center of box, ie 0.5 radius)
	base.position.set (0, 1/2., 0) //*3
	this.add (base) //*3

	// The column
	let column = new THREE.Mesh ( //*4 Column
		new THREE.CylinderGeometry (.4, .4, 2.5, 32, 32, false), pants) //*4
	column.position.set (0, 1.0 + 2.5/2.0, 0) //*4
	this.add (column) //*4

	/*
	 * Upper body
	 */

	// Just a tranform node, no geometry
	// ie root node for the rest of the upper body,
	// and then work from its local origin
	let upperbody = new THREE.Object3D () //*5 Upper body transform only
	upperbody.position.set (0, 1+2.5, 0) //*5
	this.add (upperbody) //*5

	// Trunk
	let trunk = new THREE.Mesh ( //*6 Trunk, added to upperbody not root
		new THREE.BoxGeometry (2, 3, 2), shirt) //*6
	trunk.position.set (0, 3/2., 0) //*6
	upperbody.add (trunk) //*6

	// Head
	let head = new THREE.Mesh ( //*7 Head
		new THREE.BoxGeometry (1, 1, 1), skin) //*7
	head.position.set (0, 3 + 1/2., 0) //*7
	upperbody.add (head) //*7

	// Right armhand
	let rightArmHand = new ArmHand (shirt, skin) //*8 Right armhand, separate object
	rightArmHand.position.set (-1.25, 3, 0) //*8
	upperbody.add (rightArmHand) //*8

	// Left armhand
	let leftArmHand = new ArmHand (shirt, skin) //*9 Left armhand, reuse object
	leftArmHand.position.set (1.25, 3, 0) //*9
	leftArmHand.rotation.set (0, Math.PI, 0) //*9
	upperbody.add (leftArmHand) //*9
    }
}

/*
 * Separate object that makes the Arm + Hand assembly
 */
class ArmHand extends THREE.Object3D { //*8
    constructor (shirt, skin) {
	super ()

	// Arm
	let arm = new THREE.Mesh ( //*8
		new THREE.BoxGeometry (0.5, 2.5, 0.5), shirt) //*8
	arm.position.set (0, -1.25, 0) //*8
	this.add (arm)

	// Finger
	let finger = new THREE.Mesh ( //*8
		new THREE.BoxGeometry (0.1, .5, .5), skin) //*8
	finger.position.set (-0.25+0.05, -2.5-0.25, 0) //*8
	this.add (finger)

	// Thumb
	let thumb = new THREE.Mesh ( //*8
		new THREE.BoxGeometry (0.1, .5, .5), skin) //*8
	thumb.position.set (0.25-0.1-0.05, -2.5-0.25, 0.) //*8
	this.add (thumb)
    }
}

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