robot2

robot2.html  [download]

<!DOCTYPE html>

<html>
<head>

<title>Comp 86 example: Robot, with animation and picking</title>

<script src="libs/build/three.min.js"></script>
<script src="libs/examples/js/Detector.js"></script>
<script src="libs/examples/js/controls/OrbitControls.js"></script>
<script src="boiler.js"></script>
<script src="lights.js"></script>

<script type="text/javascript">

// Global var for animation
var myScene

function makeSceneGraph () {
	myScene = new Scene()
	scene.add (myScene)
	lights = new Lights()

	// Other initialization: when the mouse moves, call our function
	document.addEventListener ("mousedown", onDocumentMouseDown, false)
}

// This will override the one in boiler.js
animate = function () { //*1 Animation
	requestAnimationFrame( animate ); //*1
	myScene.tick() //*1
	controls.update()
}

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

	// Keep our robots in a list so timer can access
	this.robots = []

	// First robot, position = WC origin to robot local origin
	this.robots[0] = new Robot ()
	this.robots[0].position.set (0, -5, -10)
	this.add (this.robots[0])

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

    /*
     * Receive timer tick, pass it to objects that need it
     */
    tick () { //*2 Propagate tick(), then render
	for (var r of this.robots) { //*2
		r.tick() //*2
	}
	render(); //*2
    }
}

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

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

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

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

	/*
	 * Upper body
	 */
	// First make root node for the rest of the upper body,
	// and then work from its local origin
	var upperbody = new THREE.Object3D ()
	upperbody.position.set (0, 1+2.5, 0)
	this.add (upperbody)

	// Trunk
	var trunk = new THREE.Mesh (
		new THREE.BoxGeometry (2, 3, 2), shirt)
	trunk.position.set (0, 1.5, 0)
	upperbody.add (trunk)

	/*
	 * Head animation:
	 * Insert an extra transform node, Identity for now,
	 * which we will modify on each tick.
	 * Things the timer needs to access are stored in ivars
	 */
	this.headMove = new THREE.Object3D () //*3 Insert animation nodes
	upperbody.add (this.headMove) //*3

	// Head
	var head = new THREE.Mesh (
		new THREE.BoxGeometry (1, 1, 1), skin)
	head.position.set (0, 3 + 1/2., 0)
	this.headMove.add (head) //*3

	// Right armhand: first we insert a transform node, from upperbody
	// local origin of armhand, so the rotation will happen at the
	// right point
	var rightArmHandTrans = new THREE.Object3D ()
	rightArmHandTrans.position.set (-1.25, 3, 0)
	upperbody.add (rightArmHandTrans) //*3

	// Animation for right armhand: again, insert transform and remember it
	// Must also remember current direction (+1 for increasing, -1 for decreasing)
	// we stash that in the userData field in scene graph node
	this.rightMove = new THREE.Object3D () //*3
	this.rightMove.userData = 1 // Our own data field //*5 Stash our data in node
	rightArmHandTrans.add (this.rightMove) //*3

	// Right armhand itself
	var rightArmHand = new ArmHand (shirt, skin)
	this.rightMove.add (rightArmHand) //*3

	// Animation for left armhand: similarly
	var leftArmHandTrans = new THREE.Object3D()
	leftArmHandTrans.position.set (1.25, 3, 0)
	leftArmHandTrans.rotation.set (0, Math.PI, 0)
	upperbody.add (leftArmHandTrans) //*3

	this.leftMove = new THREE.Object3D () //*3
	this.leftMove.userData = 1 //*5
	leftArmHandTrans.add (this.leftMove) //*3

	// Left armhand
	var leftArmHand = new ArmHand (shirt, skin)
	this.leftMove.add (leftArmHand) //*3
    }

    /*
     * Received whenever timer tick's,
     * we should update everything we need in scene graph,
     * redraw is automatic.
     */
    tick () { //*4 Change each transform
	/*
	 * Head:
	 * rotates 1 revolution (2*PI) in 9 seconds (540 1/60 sec ticks)
	 * goes round and round, rather than oscillate back and forth.
	 * We retrieve the old rotation angle out of the scene graph,
	 * modify it, and put it back in
	 */
	var a = this.headMove.rotation.y //*4
	a += (2 * Math.PI) / (9*60) //*4
	if (a > 2*Math.PI) { //*4
		a -= 2 * Math.PI //*4
	}
	this.headMove.rotation.set (0, a, 0) //*4

	/*
	 * Right armhand:
	 * rotates about (local) X axis,
	 * oscillates back and forth from -50 to +50 degrees,
	 * 5 seconds (5*60 ticks) total for the 100 (ie 2*50) degree travel.
	 * Must also remember current direction (increasing/decreasing).
	 */
	a = this.rightMove.rotation.x //*4
	a += (this.rightMove.userData * 2*50 * Math.PI/180) / (5*60) //*5
	if (Math.abs(a) > 50 * Math.PI/180) { //*5
		this.rightMove.userData *= -1; //*5
	}
	this.rightMove.rotation.set (a, 0, 0) //*4

	/*
	 * Left armhand:
	 * oscillates -30 to +30 degrees in 1 second
	 */
	a = this.leftMove.rotation.x //*4
	a += (this.leftMove.userData * 2*30 * Math.PI/180) / (1*60) //*5
	if (Math.abs(a) > 30 * Math.PI/180) { //*5
		this.leftMove.userData *= -1; //*5
	}
	this.leftMove.rotation.set (a, 0, 0) //*4
    }
}

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

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

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

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

/*
 * Handle mouse picking
 */
function onDocumentMouseDown (event) { //*6 Pick with mouse
	// Take mouse coords, flip y, convert to (-1..+1)
	// Also note in HTML: <body style="margin: 0px">
	var mouse = { //*6
		x: ( event.clientX / window.innerWidth ) * 2 - 1, //*6
		y: - ( event.clientY / window.innerHeight ) * 2 + 1 } //*6

	// Set up for picking
	var raycaster = new THREE.Raycaster () //*6
	raycaster.setFromCamera (mouse, camera) //*6

	// Returns array of all objects in scene with which the ray intersects
	var intersects = raycaster.intersectObjects (scene.children, true); //*6
	
	// If there were any intersections, take the first (ie nearest)
	if (intersects.length > 0) { //*6
		intersects[0].object.material = //*6
			new THREE.MeshPhongMaterial ({ color: 0xff0000 })
		render()
	}
}

</script>
</head>

<body style="margin: 0px">
<!-- Container for three.js -->
<div id="theContainer"></div>
</body>
</html>