<!DOCTYPE html>
<html>
<head>
<title>CS 86 example: Robot, without animation</title>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.181.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.181.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", 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", 1 * 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.1 * 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>