<!DOCTYPE html>
<html>
<head>
<title>CS 86 example: Stereo 1</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">
/*
* 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", 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);
}
}
/*
* 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>