Created
February 6, 2025 20:46
-
-
Save paulviville/562de73031fe042487ef0d6745f29ea5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import StereoCamera from './StereoScreenCamera.js'; | |
export default class Cave { | |
#screens; | |
#stereoScreenCameras; | |
constructor ( screens ) { | |
this.#screens = [...screens]; | |
this.#stereoScreenCameras = []; | |
for( const screen of screens ) { | |
// this.#stereoScreenCameras.push(new StereoCamera(screen)); | |
} | |
} | |
get screens ( ) { | |
return this.#screens; | |
} | |
get stereoScreenCameras ( ) { | |
return this.#stereoScreenCameras; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import { Object3D } from 'three'; | |
import ScreenHelper from './ScreenHelper.js'; | |
const screenColors = [0x3399DD, 0xDD3399, 0x99DD33]; | |
export default class CaveHelper extends Object3D { | |
#cave; | |
constructor ( cave ) { | |
super(); | |
this.type = 'CaveHelper'; | |
this.#cave = cave; | |
const axesHelper = new THREE.AxesHelper( 10 ); | |
this.add( axesHelper ); | |
for(const screen of this.#cave.screens) { | |
this.add(new ScreenHelper(screen, screenColors.shift())); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>CaveTracker</title> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |
<style> | |
body { margin : 0; max-width: 100%; max-height: 100%; margin: 0;} | |
canvas {width: 100%; height: 100%; max-width: 100%; max-height: 100%; margin: 0; display: block;} | |
</style> | |
</head> | |
<body> | |
<script type="importmap"> | |
{ | |
"imports": { | |
"three": "./three.module.js", | |
"three/addons/": "./jsm/" | |
} | |
} | |
</script> | |
<script type="module" src="main.js"> | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import Stats from 'three/addons/libs/stats.module.js'; | |
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | |
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
import { TrackballControls } from 'three/addons/controls/TrackballControls.js'; | |
import Screen from './Screen.js'; | |
import ScreenHelper from './ScreenHelper.js'; | |
import Cave from './Cave.js'; | |
import CaveHelper from './CaveHelper.js'; | |
import StereoScreenCamera from './StereoScreenCamera.js'; | |
import StereoScreenCameraHelper from './StereoScreenCameraHelper.js'; | |
// const socket = new WebSocket("ws://localhost:8000"); | |
// socket.addEventListener("message", (event) => { | |
// console.log("Message from server ", event.data); | |
// }); | |
// console.log(socket) | |
const stats = new Stats() | |
document.body.appendChild( stats.dom ); | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0xcccccc); | |
let ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
scene.add(ambientLight); | |
let pointLight0 = new THREE.PointLight(0xffffff, 100); | |
pointLight0.position.set(5,4,5); | |
scene.add(pointLight0); | |
const worldUp = new THREE.Vector3(0, 0, 1); | |
const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 50 ); | |
camera.up.copy(worldUp); | |
camera.position.set( -2, -4, 3 ); | |
const renderer = new THREE.WebGLRenderer({antialias: true}); | |
renderer.autoClear = false; | |
renderer.setPixelRatio( window.devicePixelRatio ); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
document.body.appendChild( renderer.domElement ); | |
const orbitControls = new OrbitControls(camera, renderer.domElement); | |
orbitControls.target.set(0, 0, 2); | |
orbitControls.update() | |
window.addEventListener('resize', function() { | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
renderer.setSize(width, height); | |
camera.aspect = width / height; | |
camera.updateProjectionMatrix(); | |
}); | |
function animate() { | |
orbitControls.update() | |
renderer.render( scene, camera ); | |
stats.update() | |
} | |
renderer.setAnimationLoop( animate ); | |
const PDS = Math.sqrt(2) * 1.8; | |
const screenCoords = [ | |
[new THREE.Vector3(-PDS, 0, 0), new THREE.Vector3(0, PDS, 0), new THREE.Vector3(-PDS, 0, 2.25)], | |
[new THREE.Vector3(0, PDS, 0), new THREE.Vector3(PDS, 0, 0), new THREE.Vector3(0, PDS, 2.25)], | |
[new THREE.Vector3(-PDS + 1.194, 1.194), new THREE.Vector3(0.62, -0.82, 0), new THREE.Vector3(-0.21, PDS - 0.21, 0)] | |
] | |
const t = new THREE.Vector3(1, 1, 0).normalize().multiplyScalar(2.25); | |
const screenCorners0 = [ | |
new THREE.Vector3(-PDS, 0, 0), | |
new THREE.Vector3(0, PDS, 0), | |
new THREE.Vector3(-PDS, 0, 2.25), | |
new THREE.Vector3(0, PDS, 2.25), | |
]; | |
const screenCorners1 = [ | |
new THREE.Vector3(0, PDS, 0), | |
new THREE.Vector3(PDS, 0, 0), | |
new THREE.Vector3( 0, PDS, 2.25), | |
new THREE.Vector3( PDS, 0, 2.25), | |
]; | |
const screenCorners2 = [ | |
new THREE.Vector3(-t.x, PDS - t.y, 0), | |
new THREE.Vector3(PDS - t.x, -t.y, 0), | |
new THREE.Vector3(0, PDS, 0), | |
new THREE.Vector3(PDS, 0, 0), | |
]; | |
const screen0 = new Screen(screenCorners0); | |
const screen1 = new Screen(screenCorners1); | |
const screen2 = new Screen(screenCorners2); | |
const cave = new Cave([screen0, screen1, screen2]); | |
const caveHelper = new CaveHelper(cave); | |
scene.add(caveHelper); | |
// const cameraMarker = new THREE.Mesh(new THREE.SphereGeometry(0.05, 16, 16), new THREE.MeshBasicMaterial({color: 0xff0000})); | |
// scene.add(cameraMarker) | |
const targetPoint = new THREE.Vector3(-1.5, PDS, 1.2) | |
const trackedCamera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 0.5 ); | |
trackedCamera.up.copy(worldUp); | |
trackedCamera.position.set(0.5, -0.1, 1); | |
trackedCamera.lookAt(targetPoint) | |
trackedCamera.updateProjectionMatrix(); | |
trackedCamera.updateWorldMatrix(); | |
const stereoScreenCamera0 = new StereoScreenCamera(screen0); | |
const stereoScreenCamera1 = new StereoScreenCamera(screen1); | |
const stereoScreenCamera2 = new StereoScreenCamera(screen2); | |
const stereoScreenCameraHelper0 = new StereoScreenCameraHelper(stereoScreenCamera0); | |
const stereoScreenCameraHelper1 = new StereoScreenCameraHelper(stereoScreenCamera1); | |
const stereoScreenCameraHelper2 = new StereoScreenCameraHelper(stereoScreenCamera2); | |
scene.add(stereoScreenCameraHelper0); | |
scene.add(stereoScreenCameraHelper1); | |
scene.add(stereoScreenCameraHelper2); | |
stereoScreenCameraHelper0.update(trackedCamera.matrixWorld.clone()); | |
stereoScreenCameraHelper1.update(trackedCamera.matrixWorld.clone()); | |
stereoScreenCameraHelper2.update(trackedCamera.matrixWorld.clone()); | |
// trackedCamera.position.set(0, 0, 1); | |
// trackedCamera.lookAt(new THREE.Vector3(0, PDS, 1.2)) | |
// trackedCamera.updateProjectionMatrix(); | |
// trackedCamera.updateWorldMatrix(); | |
// stereoScreenCameraHelper0.update(trackedCamera.matrixWorld.clone()); | |
// stereoScreenCameraHelper1.update(trackedCamera.matrixWorld.clone()); | |
// stereoScreenCameraHelper2.update(trackedCamera.matrixWorld.clone()); | |
const trackedCameraHelper = new THREE.CameraHelper(trackedCamera); | |
scene.add(trackedCameraHelper); | |
function updateRig() { | |
trackedCamera.lookAt(targetPoint); | |
trackedCamera.updateProjectionMatrix(); | |
trackedCamera.updateWorldMatrix(); | |
trackedCameraHelper.update(); | |
stereoScreenCameraHelper0.update(trackedCamera.matrixWorld.clone()); | |
stereoScreenCameraHelper1.update(trackedCamera.matrixWorld.clone()); | |
stereoScreenCameraHelper2.update(trackedCamera.matrixWorld.clone()); | |
} | |
const guiParams = { | |
updateRig: updateRig, | |
} | |
const gui = new GUI(); | |
gui.add(trackedCamera.position, 'x').name("x").min(-1.0).max(1.0).step(0.05).onChange(updateRig); | |
gui.add(trackedCamera.position, 'y').name("y").min(-PDS).max(PDS).step(0.05).onChange(updateRig); | |
gui.add(trackedCamera.position, 'z').name("z").min(0.05).max(2.0).step(0.05).onChange(updateRig); | |
gui.add(targetPoint, 'x').name('tx').min(-10.0).max(10.0).step(0.05).onChange(updateRig); | |
gui.add(targetPoint, 'y').name('ty').min(-10.0).max(10.0).step(0.05).onChange(updateRig); | |
gui.add(targetPoint, 'z').name('tz').min(-10.0).max(10.0).step(0.05).onChange(updateRig); | |
window.updateRig = updateRig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import { Vector3, Matrix4 } from 'three'; | |
/// 2 ------ 3 | |
/// | Screen | | |
/// 0 ------ 1 | |
export default class Screen { | |
#corners; | |
#ssAxes = {x: new THREE.Vector3(), y: new THREE.Vector3(), z: new THREE.Vector3()}; | |
constructor ( corners ) { | |
this.#corners = [new Vector3(-1, -1, 0), new Vector3(1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0)]; | |
if( corners !== undefined ) { | |
this.#corners[0].copy(corners[0]); | |
this.#corners[1].copy(corners[1]); | |
this.#corners[2].copy(corners[2]); | |
this.#corners[3].copy(corners[3]); | |
} | |
this.#computeScreenSpace(); | |
} | |
get corners () { | |
return [ | |
this.#corners[0].clone(), | |
this.#corners[1].clone(), | |
this.#corners[2].clone(), | |
this.#corners[3].clone(), | |
] | |
} | |
#computeScreenSpace ( ) { | |
this.#ssAxes.x.copy(this.#corners[1]).sub(this.#corners[0]).normalize(); | |
this.#ssAxes.y.copy(this.#corners[2]).sub(this.#corners[0]).normalize(); | |
this.#ssAxes.z.crossVectors(this.#ssAxes.x, this.#ssAxes.y).normalize(); | |
console.log(this.#ssAxes) | |
} | |
#eyeMatrices ( eye ) { | |
const projection = new Matrix4(); | |
const view = new Matrix4(); | |
const eye0 = this.#corners[0].clone().sub(eye); | |
const eye1 = this.#corners[1].clone().sub(eye); | |
const eye2 = this.#corners[2].clone().sub(eye); | |
const dist = - eye0.dot(this.#ssAxes.z); | |
const nearCP = 0.01; | |
/// for debug, replace with frustrum far | |
const farCP = dist; | |
const ND = nearCP / dist; | |
const l = this.#ssAxes.x.dot(eye0) * ND; | |
const r = this.#ssAxes.x.dot(eye1) * ND; | |
const b = this.#ssAxes.y.dot(eye0) * ND; | |
const t = this.#ssAxes.y.dot(eye2) * ND; | |
projection.set( | |
(2.0 * nearCP) / (r - l), 0.0, (r + l) / (r - l), 0.0, | |
0.0, (2.0 * nearCP) / (t - b), (t + b) / (t - b), 0.0, | |
0.0, 0.0, -(farCP + nearCP) / (farCP - nearCP), -(2.0 * farCP * nearCP) / (farCP - nearCP), | |
0.0, 0.0, -1.0, 0.0 | |
); | |
/// move out, constant on screen | |
const R = new Matrix4( | |
this.#ssAxes.x.x, this.#ssAxes.x.y, this.#ssAxes.x.z, 0.0, | |
this.#ssAxes.y.x, this.#ssAxes.y.y, this.#ssAxes.y.z, 0.0, | |
this.#ssAxes.z.x, this.#ssAxes.z.y, this.#ssAxes.z.z, 0.0, | |
0.0, 0.0, 0.0, 1.0 | |
); | |
const E = new Matrix4().makeTranslation(-eye.x, -eye.y, -eye.z); | |
view.multiplyMatrices(R, E); | |
return {projection, view}; | |
} | |
stereoMatrices ( headMatrix ) { | |
const eyes = { | |
left: new THREE.Vector3(-0.032, 0.0, -0.015), | |
right: new THREE.Vector3(0.032, 0.0, -0.015) | |
}; | |
eyes.left.applyMatrix4(headMatrix); | |
eyes.right.applyMatrix4(headMatrix); | |
const leftMatrices = this.#eyeMatrices(eyes.left); | |
eyes.leftView = leftMatrices.view; | |
eyes.leftProjection = leftMatrices.projection; | |
const rightMatrices = this.#eyeMatrices(eyes.right); | |
eyes.rightView = rightMatrices.view; | |
eyes.rightProjection = rightMatrices.projection; | |
return eyes; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import { Object3D } from 'three'; | |
import Screen from './Screen.js'; | |
export default class ScreenHelper extends Object3D { | |
#screen; | |
#screenFace; | |
#screenEdge; | |
constructor ( screen, color = 0xFFFFFF ) { | |
super(); | |
this.type = 'ScreenHelper'; | |
this.#screen = screen; | |
const corners = screen.corners; | |
const indices = [ 0, 1, 2, 1, 3, 2 ] | |
const vertices = new Float32Array([ | |
...corners[0].toArray(), | |
...corners[1].toArray(), | |
...corners[2].toArray(), | |
...corners[3].toArray() | |
]); | |
const geometry = new THREE.BufferGeometry(); | |
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); | |
geometry.setIndex(indices); | |
geometry.computeVertexNormals(); | |
const material = new THREE.MeshBasicMaterial({color: color, side: THREE.DoubleSide, transparent: true, opacity: 0.2}); | |
this.#screenFace = new THREE.Mesh(geometry, material); | |
const edgeGeometry = new THREE.EdgesGeometry(geometry); | |
const edgeMaterial = new THREE.LineBasicMaterial({color: 0x000000}); | |
this.#screenEdge = new THREE.LineSegments(edgeGeometry, edgeMaterial); | |
this.add(this.#screenEdge, this.#screenFace); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
import { Vector3, Quaternion, Matrix4 } from 'three'; | |
export default class StereoScreenCamera { | |
#screen; | |
#eyeSeparation = 0.064; | |
#position = new THREE.Vector3(); | |
#quaternion = new THREE.Quaternion(); | |
#left = new THREE.PerspectiveCamera(); | |
#right = new THREE.PerspectiveCamera(); | |
/// Screen Space Axes | |
#ssX = new THREE.Vector3(); | |
#ssY = new THREE.Vector3(); | |
#ssZ = new THREE.Vector3(); | |
#ssRotation = new THREE.Matrix4(); | |
constructor ( screen ) { | |
this.#screen = screen; | |
this.#computeScreenSpace(); | |
} | |
get worldMatrix ( ) { | |
// m4d << r[0], r[4], r[8], t.pos[0], r[1], r[5], r[9], t.pos[1], r[2], r[6], r[10], t.pos[2], 0.0f, 0.0f, 0.0f, 1; | |
// CaveViewer::head_matrix_s_ = m4d.cast<float>(); | |
} | |
set position ( position ) { | |
this.#position.copy(position); | |
} | |
set quaternion ( quaternion ) { | |
this.#quaternion.copy(quaternion); | |
} | |
get left ( ) { | |
return this.#left; | |
} | |
get right ( ) { | |
return this.#right; | |
} | |
get eyeSeperation ( ) { | |
return this.#eyeSeparation; | |
} | |
set eyeSeperation ( dist ) { | |
this.#eyeSeparation = dist; | |
} | |
update ( headMatrix ) { | |
const leftEye = new THREE.Vector3(-this.#eyeSeparation / 2, 0.0, -0.015); | |
const rightEye = new THREE.Vector3(this.#eyeSeparation / 2, 0.0, -0.015); | |
leftEye.applyMatrix4(headMatrix); | |
rightEye.applyMatrix4(headMatrix); | |
const leftMatrices = this.#eyeMatrices(leftEye); | |
const rightMatrices = this.#eyeMatrices(rightEye); | |
this.#left.matrixWorldInverse.copy(leftMatrices.view); | |
this.#left.matrixWorld.copy(leftMatrices.view.clone().invert()); | |
this.#left.projectionMatrix.copy(leftMatrices.projection); | |
this.#left.projectionMatrixInverse.copy(leftMatrices.projection.clone().invert() ); | |
// this.#left.updateProjectionMatrix = false; | |
this.#right.matrixWorldInverse.copy(rightMatrices.view); | |
this.#right.matrixWorld.copy(rightMatrices.view.clone().invert()); | |
this.#right.projectionMatrix.copy(rightMatrices.projection); | |
this.#right.projectionMatrixInverse.copy(rightMatrices.projection.clone().invert() ); | |
// this.#right.updateProjectionMatrix = false; | |
} | |
#computeScreenSpace ( ) { | |
const corners = this.#screen.corners; | |
this.#ssX.copy(corners[1]).sub(corners[0]).normalize(); | |
this.#ssY.copy(corners[2]).sub(corners[0]).normalize(); | |
this.#ssZ.crossVectors(this.#ssX, this.#ssY).normalize(); | |
this.#ssRotation.set( | |
this.#ssX.x, this.#ssX.y, this.#ssX.z, 0.0, | |
this.#ssY.x, this.#ssY.y, this.#ssY.z, 0.0, | |
this.#ssZ.x, this.#ssZ.y, this.#ssZ.z, 0.0, | |
0.0, 0.0, 0.0, 1.0 | |
); | |
} | |
#eyeMatrices ( eye ) { | |
const projection = new Matrix4(); | |
const view = new Matrix4(); | |
const corners = this.#screen.corners; | |
const eye0 = corners[0].clone().sub(eye); | |
const eye1 = corners[1].clone().sub(eye); | |
const eye2 = corners[2].clone().sub(eye); | |
const dist = - eye0.dot(this.#ssZ); | |
const nearCP = 0.01; | |
// const farCP = 10.0; | |
const farCP = dist; | |
const ND = nearCP / dist; | |
const l = this.#ssX.dot(eye0) * ND; | |
const r = this.#ssX.dot(eye1) * ND; | |
const b = this.#ssY.dot(eye0) * ND; | |
const t = this.#ssY.dot(eye2) * ND; | |
projection.set( | |
(2.0 * nearCP) / (r - l), 0.0, (r + l) / (r - l), 0.0, | |
0.0, (2.0 * nearCP) / (t - b), (t + b) / (t - b), 0.0, | |
0.0, 0.0, -(farCP + nearCP) / (farCP - nearCP), -(2.0 * farCP * nearCP) / (farCP - nearCP), | |
0.0, 0.0, -1.0, 0.0 | |
); | |
const E = new Matrix4().makeTranslation(-eye.x, -eye.y, -eye.z); | |
view.multiplyMatrices(this.#ssRotation, E); | |
return {projection, view}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import StereoCamera from "./StereoScreenCamera.js"; | |
import { Object3D } from "three"; | |
import { CameraHelper } from "./three.module.js"; | |
export default class StereoScreenCameraHelper extends Object3D{ | |
#stereoScreenCamera; | |
#leftCameraHelper; | |
#rightCameraHelper; | |
constructor ( stereoCamera ) { | |
super(); | |
this.#stereoScreenCamera = stereoCamera; | |
this.#leftCameraHelper = new CameraHelper(this.#stereoScreenCamera.left); | |
this.#rightCameraHelper = new CameraHelper(this.#stereoScreenCamera.right); | |
this.add(this.#leftCameraHelper, this.#rightCameraHelper); | |
} | |
update ( headMatrix ) { | |
this.#stereoScreenCamera.update(headMatrix); | |
this.#leftCameraHelper.update(); | |
this.#rightCameraHelper.update(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment