Stereo example: stereo.html

<!DOCTYPE html>

<html>
<head>

<title>Comp 86 example: Stereo 1</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">

/*
 * Example: Stereo, homemade version, without library 
 *
 * Uses 2 cameras but 1 scene 
 * Crude approach, camera geometry may be slightly off, 
 * built in library version does it better. 
 */

/*
 * Same scene as previous code
 */
class SceneGraph extends THREE.Scene {
    constructor () {
	super ()

	// First item = a box, rotated and translated
	let box = new THREE.Mesh (
		new THREE.BoxGeometry (1, 3, 1),
		new THREE.MeshPhongMaterial ({ color: "yellow" }))

	// Move it back from origin and to the right
	box.position.set (5, 0, -4)

	// Rotate it in X and Y
	box.rotation.set (45 * Math.PI/180, 30 * Math.PI/180, 0)

	// Plug it in to our scene
	this.add (box)

	// A sphere, in a different location
	let sphere = new THREE.Mesh (
		new THREE.SphereGeometry(1, 32, 32),
		new THREE.MeshPhongMaterial ({
			color: "red", shininess: 60 }))
	sphere.position.set (-5, 0, -3)
	this.add (sphere)

	// A pyramid, using our subroutine below
	let pyramid = new Pyramid (new THREE.MeshPhongMaterial ({
		color: "green", shininess: 60 }))
	pyramid.position.set (0, 0, -6)
	pyramid.rotation.set (0, 45 * Math.PI/180, 0)
	this.add (pyramid)
    }
}

/*
 * Same as previous example
 */
class Pyramid extends THREE.Object3D {
    constructor (material) {
	super ()

	// Bottom Box, located slightly down
	// (relative to origin of the pyramid)
	let bot = new THREE.Mesh (
		new THREE.BoxGeometry (3, 3, 3),
		material)
	bot.position.set (0, -3/2, 0)
	this.add (bot)

	// Middle Box, slightly up
	let mid = new THREE.Mesh (
		new THREE.BoxGeometry (2, 2, 2),
		material)
	mid.position.set (0, 2/2, 0)
	this.add (mid)

	// Top box, further up
	let top = new THREE.Mesh (
		new THREE.BoxGeometry (1, 1, 1),
		material)
	top.position.set (0, 2 + 1/2, 0)
	this.add (top)
    }
}

/*
 * 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);
    }
}

/*
 * New version of usual boilerplate, to handle stereo
 */

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

class Main {
	constructor () {
		this.eyeOffset = 20.5/12 // ie sort of as if WC units = feet

		this.renderer1 = new THREE.WebGLRenderer( { antialias: true } ); //*1 2 renderers
		this.renderer1.setClearColor( new THREE.Color ("lightgrey"))
		this.renderer2 = new THREE.WebGLRenderer( { antialias: true } ); //*1
		this.renderer2.setClearColor( new THREE.Color ("lightgrey"))
		this.size = window.innerWidth/2 - 50
		this.renderer1.setSize( this.size, this.size );
		this.renderer2.setSize( this.size, this.size );
		document.body.appendChild( this.renderer1.domElement ); //*1
		document.body.appendChild( this.renderer2.domElement ); //*1
		this.renderer1.domElement.style = "border-style: solid; border-color: black; border-width: 1px";
		this.renderer2.domElement.style = "border-style: solid; border-color: black; border-width: 1px";

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

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

		// Put 2 cameras into the scene
		this.camera1 = new THREE.PerspectiveCamera( 60, 1, 1, 1000 ); //*2 2 cameras
		this.camera2 = new THREE.PerspectiveCamera( 60, 1, 1, 1000 ); //*2
		this.camera1.position.set(0, 0, 5); //*2
		this.camera2.position.set(this.eyeOffset, 0, 5); //*2
		let direction = new THREE.Vector3() 
		this.camera1.getWorldDirection(direction) 
		this.camera2.lookAt (direction) 

		// Create one camera (ie head) contol
		new OrbitControls( this.camera1, this.renderer1.domElement );

		// Start animation loop
		this.animate()

		// In case window is resized
		window.onresize = () => this.onResize()
	}

	// Animation loop
	animate() { //*3 camera2 position = camera1 + eye offset
		requestAnimationFrame (() => this.animate());

		// Take the latest camera1 position and set camera2 based on it
		this.camera2.position.set (this.camera1.position.x + this.eyeOffset, this.camera1.position.y, this.camera1.position.z) //*3
		let dir = new THREE.Vector3() 
		this.camera1.getWorldDirection(dir) 
		this.camera2.lookAt (dir) 

		this.render()
	}

	// Render the scene on both cameras
	render() { //*4 Render same scene on both cameras
		this.renderer1.render( this.scene, this.camera1 ); //*4
		this.renderer2.render( this.scene, this.camera2 ); //*4
	}

	// In case window is resized
	onResize () {
		this.renderer1.setSize( this.size, this.size );
		this.renderer2.setSize( this.size, this.size );

		this.camera1.aspect = 1
		this.camera2.aspect = 1
		this.camera1.updateProjectionMatrix();
		this.camera2.updateProjectionMatrix();

		this.render();
	}
}

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

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