// Cloned by MENGTE ZHU on 7 Nov 2023 from World "Pointer Lock" by Michael Walsh
// Please leave this clone trail here.
// imported from https://threejs.org/examples/misc_controls_pointerlock.html
import * as THREE from '/uploads/mjwalsh/three.module.js';
'use strict';
let camera, scene, renderer, controls;
const objects = [];
let raycaster;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let canJump = false;
let prevTime = performance.now();
const velocity = new THREE.Vector3();
const vertex = new THREE.Vector3();
const color = new THREE.Color();
const onKeyDown = function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
moveForward = true;
break;
case 'ArrowLeft':
moveLeft = true;
break;
case 'ArrowDown':
moveBackward = true;
break;
case 'ArrowRight':
moveRight = true;
break;
case 'Space':
if ( canJump === true ) velocity.y += 350;
canJump = false;
break;
}
};
const onKeyUp = function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
moveForward = false;
break;
case 'ArrowLeft':
moveLeft = false;
break;
case 'ArrowDown':
moveBackward = false;
break;
case 'ArrowRight':
moveRight = false;
break;
}
};
document.addEventListener( 'keydown', onKeyDown );
document.addEventListener( 'keyup', onKeyUp );
function init()
{
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100000);
camera.position.y = 5;
camera.position.z = 5;
camera.position.x = 0;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );
scene.fog = new THREE.Fog( 0xffffff, 0, 750 );
scene.add(new THREE.AmbientLight(0xffffff, 0.7))
const dirLight = new THREE.DirectionalLight(0xffffff, 1)
dirLight.position.set(- 60, 100, - 10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 50;
dirLight.shadow.camera.bottom = - 50;
dirLight.shadow.camera.left = - 50;
dirLight.shadow.camera.right = 50;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 200;
dirLight.shadow.mapSize.width = 4096;
dirLight.shadow.mapSize.height = 4096;
scene.add(dirLight);
controls = new PointerLockControls( camera );
raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );
// floor
let floorGeometry = new THREE.PlaneGeometry( 2000, 2000, 100, 100 );
floorGeometry.rotateX( - Math.PI / 2 );
// vertex displacement
let position = floorGeometry.attributes.position;
for ( let i = 0, l = position.count; i < l; i ++ ) {
vertex.fromBufferAttribute( position, i );
vertex.x += Math.random() * 20 - 10;
vertex.y += Math.random() * 2;
vertex.z += Math.random() * 20 - 10;
position.setXYZ( i, vertex.x, vertex.y, vertex.z );
}
floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
position = floorGeometry.attributes.position;
const colorsFloor = [];
for ( let i = 0, l = position.count; i < l; i ++ ) {
color.setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75, THREE.SRGBColorSpace );
colorsFloor.push( color.r, color.g, color.b );
}
floorGeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colorsFloor, 3 ) );
const floorMaterial = new THREE.MeshBasicMaterial( { vertexColors: true } );
const floor = new THREE.Mesh( floorGeometry, floorMaterial );
scene.add( floor );
//
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
const time = performance.now();
raycaster.ray.origin.copy( camera.position );
raycaster.ray.origin.y -= 10;
const intersections = raycaster.intersectObjects( objects, false );
const onObject = intersections.length > 0;
const delta = ( time - prevTime ) / 1000;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
const direction = new THREE.Vector3();
direction.z = Number( moveForward ) - Number( moveBackward );
direction.x = Number( moveRight ) - Number( moveLeft );
direction.normalize(); // this ensures consistent movements in all directions
if ( moveForward || moveBackward ) velocity.z -= direction.z * 400.0 * delta;
// we're standing on an object
if ( onObject === true ) {
velocity.y = Math.max( 0, velocity.y );
canJump = true;
}
// rotate the camera using the left and right arrows
if ( direction.x != 0 ) {
camera.rotation.x = 0;
camera.rotation.y -= direction.x * 0.03;
camera.rotation.z = 0;
}
controls.moveForward( - velocity.z * delta );
camera.position.y += ( velocity.y * delta ); // new behavior
if ( camera.position.y < 10 ) {
velocity.y = 0;
camera.position.y = 10;
canJump = true;
}
prevTime = time;
renderer.render( scene, camera );
}
class PointerLockControls extends THREE.EventDispatcher {
euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
vector = new THREE.Vector3();
changeEvent = { type: 'change' };
lockEvent = { type: 'lock' };
unlockEvent = { type: 'unlock' };
PI_2 = Math.PI / 2;
constructor( camera ) {
super();
this.camera = camera;
// Set to constrain the pitch of the camera
// Range is 0 to Math.PI radians
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
this.pointerSpeed = 1.0;
this.connect();
}
connect() {
document.addEventListener( 'pointerlockchange', this.onPointerlockChange );
document.addEventListener( 'pointerlockerror', this.onPointerlockError );
}
disconnect() {
document.removeEventListener( 'pointerlockchange', this.onPointerlockChange );
document.removeEventListener( 'pointerlockerror', this.onPointerlockError );
document.removeEventListener( 'mousemove', this.onMouseMove );
}
dispose() {
this.disconnect();
}
getObject() { // retaining this method for backward compatibility
return this.camera;
}
getDirection( v ) {
return v.set( 0, 0, - 1 ).applyQuaternion( this.camera.quaternion );
}
moveForward( distance ) {
// move forward parallel to the xz-plane
// assumes camera.up is y-up
this.vector.setFromMatrixColumn( this.camera.matrix, 0 );
this.vector.crossVectors( this.camera.up, this.vector );
this.camera.position.addScaledVector( this.vector, distance );
}
moveRight( distance ) {
this.vector.setFromMatrixColumn( this.camera.matrix, 0 );
this.camera.position.addScaledVector( this.vector, distance );
}
lock() {
document.body.requestPointerLock();
}
unlock() {
document.exitPointerLock();
}
// event listeners
onMouseMove = (event) => {
const movementX = event.movementX || 0;
const movementY = event.movementY || 0;
this.euler.setFromQuaternion( this.camera.quaternion );
this.euler.y -= movementX * 0.002 * this.pointerSpeed;
this.euler.x -= movementY * 0.002 * this.pointerSpeed;
this.euler.x = Math.max( this.PI_2 - this.maxPolarAngle, Math.min( this.PI_2 - this.minPolarAngle, this.euler.x ) );
this.camera.quaternion.setFromEuler( this.euler );
this.dispatchEvent( this.changeEvent );
};
onPointerlockChange = () => {
if ( document.pointerLockElement === document.body ) {
document.addEventListener( 'mousemove', this.onMouseMove );
this.dispatchEvent( this.lockEvent );
} else {
this.dispatchEvent( this.unlockEvent );
document.removeEventListener( 'mousemove', this.onMouseMove );
}
};
onPointerlockError = () => {
console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
};
}
init();
animate();